<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>小马哥的故事</title>
    <link>https://maruifu.cn</link>
    <description>马瑞富,小马哥的故事,小马哥博客</description>
    <language>zh-CN</language>
    <item>
      <title>企业级代理架构实践指南：从浏览器到远程隧道的优雅方案</title>
      <link>https://maruifu.cn/article/361</link>
      <content:encoded>&lt;h2&gt;引言&lt;/h2&gt; &lt;p&gt;在现代网络架构中,代理技术不仅是访问控制的核心组件,更是构建安全、高效网络环境的关键基础设施。本文将从架构视角,系统性地阐述两种轻量级代理方案的设计与实现:浏览器级的精准流量调度与 SSH 隧道的安全代理转发。&lt;/p&gt; &lt;h2&gt;一、浏览器代理编排：ZeroOmega 的架构设计&lt;/h2&gt; &lt;h3&gt;1.1 设计理念：职责分离原则&lt;/h3&gt; &lt;p&gt;ZeroOmega (SwitchyOmega 3) 采用了 &lt;strong&gt;代理编排器&lt;/strong&gt; 的设计模式,其核心价值在于:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;单一职责&lt;/strong&gt;：专注于流量调度逻辑,不承载代理节点本身&lt;/li&gt; &lt;li&gt;&lt;strong&gt;精准分流&lt;/strong&gt;：基于规则的智能路由,实现细粒度的流量控制&lt;/li&gt; &lt;li&gt;&lt;strong&gt;非侵入式&lt;/strong&gt;：仅管控浏览器流量,保持系统其他组件的网络独立性&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这种设计符合 &lt;strong&gt;微服务架构中的网关模式&lt;/strong&gt;,将代理决策与代理执行解耦,提供了更高的灵活性和可维护性。&lt;/p&gt; &lt;h3&gt;1.2 快速部署方案&lt;/h3&gt; &lt;h4&gt;第一步：组件安装&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 方案A: Chrome Web Store (推荐) # 搜索 &amp;quot;Proxy SwitchyOmega 3 (ZeroOmega)&amp;quot;  # 方案B: 离线安装 # GitHub Release: https://github.com/zero-peak/ZeroOmega/releases &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;第二步：代理配置&lt;/h4&gt; &lt;p&gt;在扩展选项页配置代理服务器:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置参数&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;协议选择&lt;/strong&gt;: HTTP (兼容性最优) 或 SOCKS5 (性能更优)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务端点&lt;/strong&gt;: &lt;code&gt;127.0.0.1&lt;/code&gt; (本地回环地址)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;端口映射&lt;/p&gt; &lt;p&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Clash 默认: &lt;code&gt;7890&lt;/code&gt; (HTTP) / &lt;code&gt;7891&lt;/code&gt; (SOCKS5)&lt;/li&gt; &lt;li&gt;v2ray 默认: &lt;code&gt;1080&lt;/code&gt; (SOCKS5)&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;架构建议&lt;/strong&gt;:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-yaml"&gt;# 推荐配置结构 Proxy_Profiles:   - name: &amp;quot;Production&amp;quot;     protocol: HTTP     host: 127.0.0.1     port: 7890      - name: &amp;quot;Development&amp;quot;       protocol: SOCKS5     host: 127.0.0.1     port: 7891 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;1.3 分流规则设计&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;最佳实践规则集&lt;/strong&gt;:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;// 基于域名的智能路由 {   &amp;quot;RuleSet&amp;quot;: [     {       &amp;quot;condition&amp;quot;: &amp;quot;DomainSuffix&amp;quot;,       &amp;quot;pattern&amp;quot;: &amp;quot;google.com&amp;quot;,       &amp;quot;profile&amp;quot;: &amp;quot;Proxy&amp;quot;,       &amp;quot;comment&amp;quot;: &amp;quot;国际服务走代理&amp;quot;     },     {       &amp;quot;condition&amp;quot;: &amp;quot;DomainSuffix&amp;quot;,        &amp;quot;pattern&amp;quot;: &amp;quot;taobao.com&amp;quot;,       &amp;quot;profile&amp;quot;: &amp;quot;Direct&amp;quot;,       &amp;quot;comment&amp;quot;: &amp;quot;国内电商直连&amp;quot;     },     {       &amp;quot;condition&amp;quot;: &amp;quot;DomainKeyword&amp;quot;,       &amp;quot;pattern&amp;quot;: &amp;quot;localhost&amp;quot;,       &amp;quot;profile&amp;quot;: &amp;quot;Direct&amp;quot;,       &amp;quot;comment&amp;quot;: &amp;quot;本地开发环境&amp;quot;     }   ] } &lt;/code&gt;&lt;/pre&gt; &lt;hr /&gt; &lt;h2&gt;二、SSH 隧道代理：轻量级安全通道&lt;/h2&gt; &lt;h3&gt;2.1 架构优势分析&lt;/h3&gt; &lt;p&gt;SSH 动态端口转发 (Dynamic Port Forwarding) 提供了:&lt;/p&gt; &lt;p&gt;✅ &lt;strong&gt;零依赖&lt;/strong&gt;: 无需安装额外代理软件 ✅ &lt;strong&gt;加密传输&lt;/strong&gt;: 基于SSH协议的端到端加密 ✅ &lt;strong&gt;灵活部署&lt;/strong&gt;: 任意SSH服务器均可作为代理节点 ✅ &lt;strong&gt;SOCKS5原生支持&lt;/strong&gt;: 标准协议,兼容性强&lt;/p&gt; &lt;h3&gt;2.2 核心配置&lt;/h3&gt; &lt;h4&gt;基础实现&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 标准部署命令 ssh -D 1080 -N -f user@your-server.com  # 参数解析: # -D 1080    创建本地SOCKS5代理监听端口 # -N         禁用远程命令执行(安全加固) # -f         后台守护进程模式 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;安全增强：密钥认证&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 1. 密钥对生成(推荐Ed25519算法) ssh-keygen -t ed25519 -C &amp;quot;proxy@your-domain&amp;quot;  # 2. 公钥部署 ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server.com  # 3. 连接测试 ssh -o BatchMode=yes user@your-server.com exit &amp;amp;&amp;amp; echo &amp;quot;Auth Success&amp;quot;  # 4. 生产环境部署 ssh -D 1080 -N -f \     -o ServerAliveInterval=60 \     -o ServerAliveCountMax=3 \     -o ExitOnForwardFailure=yes \     user@your-server.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;高级参数说明&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;ServerAliveInterval&lt;/code&gt;: 心跳检测间隔(秒),防止连接超时断开&lt;/li&gt; &lt;li&gt;&lt;code&gt;ServerAliveCountMax&lt;/code&gt;: 最大失败次数&lt;/li&gt; &lt;li&gt;&lt;code&gt;ExitOnForwardFailure&lt;/code&gt;: 端口转发失败时立即退出&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;2.3 运维管理&lt;/h3&gt; &lt;h4&gt;进程监控&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 方案A: 传统进程管理 ps aux | grep &amp;quot;ssh -D&amp;quot; | grep -v grep  # 方案B: 端口监听检测 lsof -i :1080 -P -n | grep LISTEN  # 方案C: 系统级服务(推荐) # 创建 systemd 服务实现自动重启 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;连接验证&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 代理功能测试 curl --socks5 127.0.0.1:1080 \      --connect-timeout 5 \      https://api.ipify.org  # 期望输出: 服务器公网IP  # 完整性测试 curl --socks5 127.0.0.1:1080 \      -I https://www.google.com 2&amp;gt;&amp;amp;1 | head -1 # 期望输出: HTTP/2 200 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;优雅停止&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 精准停止 pkill -f &amp;quot;ssh -D 1080&amp;quot;  # 或基于PID kill $(lsof -ti :1080) &lt;/code&gt;&lt;/pre&gt; &lt;hr /&gt; &lt;h2&gt;三、工程化实践：命令行工具封装&lt;/h2&gt; &lt;h3&gt;3.1 Shell Alias 配置&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# ~/.zshrc 或 ~/.bashrc  # 代理管理套件 alias proxy-on='ssh -D 1080 -N -f user@your-server.com' alias proxy-off='pkill -f &amp;quot;ssh -D 1080&amp;quot;' alias proxy-status='lsof -i :1080 -P -n' alias proxy-test='curl --socks5 127.0.0.1:1080 https://api.ipify.org'  # 重载配置 source ~/.zshrc &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;3.2 自动化脚本示例&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash # proxy-manager.sh - 代理生命周期管理  PROXY_PORT=1080 SERVER=&amp;quot;user@your-server.com&amp;quot;  start_proxy() {     if lsof -i :$PROXY_PORT &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then         echo &amp;quot;⚠️  Proxy already running on port $PROXY_PORT&amp;quot;         return 1     fi          ssh -D $PROXY_PORT -N -f \         -o ServerAliveInterval=60 \         -o ExitOnForwardFailure=yes \         $SERVER          sleep 2     if lsof -i :$PROXY_PORT &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then         echo &amp;quot;✅ Proxy started successfully&amp;quot;         proxy-test     else         echo &amp;quot;❌ Failed to start proxy&amp;quot;         return 1     fi }  stop_proxy() {     pkill -f &amp;quot;ssh -D $PROXY_PORT&amp;quot;     echo &amp;quot;🛑 Proxy stopped&amp;quot; }  case &amp;quot;${1:-status}&amp;quot; in     start)   start_proxy ;;     stop)    stop_proxy ;;     status)  lsof -i :$PROXY_PORT -P -n ;;     restart) stop_proxy; sleep 1; start_proxy ;;     *)       echo &amp;quot;Usage: $0 {start|stop|status|restart}&amp;quot; ;; esac &lt;/code&gt;&lt;/pre&gt; &lt;hr /&gt; &lt;h2&gt;四、架构选型建议&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;维度&lt;/th&gt;&lt;th align="left"&gt;ZeroOmega&lt;/th&gt;&lt;th align="left"&gt;SSH 隧道&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;浏览器精细化分流&lt;/td&gt;&lt;td align="left"&gt;系统级全局代理&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;部署复杂度&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;⭐⭐&lt;/td&gt;&lt;td align="left"&gt;⭐⭐⭐⭐⭐&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;灵活性&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;⭐⭐⭐⭐⭐&lt;/td&gt;&lt;td align="left"&gt;⭐⭐⭐&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;性能开销&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;极低&lt;/td&gt;&lt;td align="left"&gt;低(加密开销)&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;运维成本&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;低&lt;/td&gt;&lt;td align="left"&gt;中(需维护SSH连接)&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;strong&gt;安全等级&lt;/strong&gt;&lt;/td&gt;&lt;td align="left"&gt;继承本地代理配置&lt;/td&gt;&lt;td align="left"&gt;高(SSH加密)&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;hr /&gt; &lt;h2&gt;五、最佳实践总结&lt;/h2&gt; &lt;h3&gt;5.1 组合方案推荐&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;生产环境架构: ┌─────────────────────────────────────┐ │  Browser → ZeroOmega (Rule Engine)  │ │              ↓                       │ │    Proxy ← SSH Tunnel → Remote      │ │              ↓                       │ │         Target Services             │ └─────────────────────────────────────┘ &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;5.2 关键要点&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;最小权限原则&lt;/strong&gt;: SSH代理使用专用账号,限制shell权限&lt;/li&gt; &lt;li&gt;&lt;strong&gt;连接复用&lt;/strong&gt;: 启用SSH ControlMaster减少连接开销&lt;/li&gt; &lt;li&gt;&lt;strong&gt;监控告警&lt;/strong&gt;: 定期检测代理可用性&lt;/li&gt; &lt;li&gt;&lt;strong&gt;文档化&lt;/strong&gt;: 维护proxy-config文档,记录规则变更&lt;/li&gt; &lt;/ol&gt; &lt;hr /&gt; &lt;h2&gt;结语&lt;/h2&gt; &lt;p&gt;代理架构的设计需要在 &lt;strong&gt;易用性、安全性、可维护性&lt;/strong&gt; 之间寻找平衡。ZeroOmega 与 SSH 隧道各有所长,前者适合浏览器级别的精细控制,后者适合需要加密传输的场景。在实际工程中,建议根据业务需求选择合适方案,或采用组合架构实现最优效果。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;strong&gt;相关资源&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/zero-peak/ZeroOmega" target="_blank"&gt;ZeroOmega GitHub&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://man.openbsd.org/ssh#D" target="_blank"&gt;SSH Manual (Dynamic Forwarding)&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc1928" target="_blank"&gt;SOCKS Protocol RFC 1928&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Thu, 26 Mar 2026 02:27:00 GMT</pubDate>
    </item>
    <item>
      <title>Mac系统下IDEA的shit+cmd+f快捷键冲突问题</title>
      <link>https://maruifu.cn/article/359</link>
      <content:encoded>&lt;h2&gt;问题&lt;/h2&gt; &lt;p&gt;在IDEA中使用快捷键&lt;code&gt;shit+cmd+f&lt;/code&gt;就会跳出IDEA，变到访达到界面。每次想在IDEA中全局搜索时都会跳转到访达，必须得再手动切回IDEA，很麻烦。IDEA也会提示快捷键和操作系统快捷键冲突。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/12/25/image-20251225152303948.png" alt="image-20251225152303948" title="image-20251225152303948" /&gt;&lt;/p&gt; &lt;h2&gt;解决方法&lt;/h2&gt; &lt;p&gt;&lt;code&gt;键盘-&amp;gt;键盘快捷键-&amp;gt;搜索-&amp;gt;聚焦&lt;/code&gt;   取消勾选&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/12/25/image-20251225153557289.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/12/25/image-20251225153656707.png" alt="image-20251225153656707" title="image-20251225153656707" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 25 Dec 2025 07:38:26 GMT</pubDate>
    </item>
    <item>
      <title>Mac启动台里的图标删不掉？试试这个终极解决方案</title>
      <link>https://maruifu.cn/article/357</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;你是否曾经遇到过这样的困扰：在Mac的启动台(Launchpad)中，有些应用的图标就是删不掉？无论你是拖到废纸篓还是使用其他方法，它们就像顽固的污渍一样留在那里。别担心，我来分享一个有效的解决方案！&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;问题描述&lt;/h2&gt; &lt;p&gt;通常情况下，我们可以通过以下方式删除启动台中的图标：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;长按图标直到抖动，然后点击&amp;quot;X&amp;quot;按钮&lt;/li&gt; &lt;li&gt;直接将图标拖到废纸篓&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;但有时候，某些应用（特别是那些没有正确安装或卸载的应用）的图标会卡在启动台中，无法通过常规方法删除。&lt;/p&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;p&gt;这里是一个通过终端命令直接操作启动台数据库的方法：&lt;/p&gt; &lt;p&gt;bash&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sqlite3 $(find /private/var/folders \( -name com.apple.dock.launchpad -a -user $USER \) 2&amp;gt; /dev/null)/db/db &amp;quot;DELETE FROM apps WHERE title like '%Epic%';&amp;quot; &amp;amp;&amp;amp; killall Dock &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;命令详解&lt;/h3&gt; &lt;p&gt;让我来解释一下这个命令的各个部分：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;sqlite3&lt;/code&gt;&lt;/strong&gt; - 用于操作SQLite数据库的命令行工具&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;$(find ...)&lt;/code&gt;&lt;/strong&gt; - 查找启动台数据库的路径&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;/private/var/folders&lt;/code&gt;&lt;/strong&gt; - Mac存储用户特定数据的目录&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;com.apple.dock.launchpad&lt;/code&gt;&lt;/strong&gt; - 启动台的数据文件夹&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;db/db&lt;/code&gt;&lt;/strong&gt; - 实际的数据库文件&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;DELETE FROM apps WHERE title like '%Epic%'&lt;/code&gt;&lt;/strong&gt; - SQL命令，删除标题包含&amp;quot;Epic&amp;quot;的应用记录&lt;/li&gt; &lt;li&gt;&lt;strong&gt;&lt;code&gt;killall Dock&lt;/code&gt;&lt;/strong&gt; - 重启Dock进程，使更改生效&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;使用方法&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;打开&lt;strong&gt;终端&lt;/strong&gt;应用（可以在Spotlight中搜索&amp;quot;终端&amp;quot;）&lt;/li&gt; &lt;li&gt;复制上面的命令&lt;/li&gt; &lt;li&gt;将命令中的&lt;code&gt;'%Epic%'&lt;/code&gt;替换为你想删除的应用名称 &lt;ul&gt; &lt;li&gt;例如，要删除名为&amp;quot;TestApp&amp;quot;的应用：&lt;code&gt;'%TestApp%'&lt;/code&gt;&lt;/li&gt; &lt;li&gt;要删除名为&amp;quot;Old Software&amp;quot;的应用：&lt;code&gt;'%Old Software%'&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;按回车执行命令&lt;/li&gt; &lt;li&gt;等待Dock重启（屏幕可能会闪烁一下）&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;注意事项&lt;/h3&gt; &lt;p&gt;⚠️ &lt;strong&gt;重要提示&lt;/strong&gt;：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;在执行此命令前，请确保已正确卸载相关应用&lt;/li&gt; &lt;li&gt;此操作直接修改系统数据库，请谨慎使用&lt;/li&gt; &lt;li&gt;建议先备份重要数据&lt;/li&gt; &lt;li&gt;如果不确定应用的确切名称，可以先使用查询命令：&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;bash&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 先查看数据库中有哪些应用 sqlite3 $(find /private/var/folders \( -name com.apple.dock.launchpad -a -user $USER \) 2&amp;gt; /dev/null)/db/db &amp;quot;SELECT title FROM apps;&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;其他可能的解决方案&lt;/h2&gt; &lt;p&gt;如果上述方法不适用，你还可以尝试：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;重置启动台&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;bash&lt;/p&gt; &lt;pre&gt;&lt;code&gt;defaults write com.apple.dock ResetLaunchPad -bool true &amp;amp;&amp;amp; killall Dock &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;清理启动台缓存&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;bash&lt;/p&gt; &lt;pre&gt;&lt;code&gt;rm ~/Library/Application\ Support/Dock/*.db &amp;amp;&amp;amp; killall Dock &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;顽固的启动台图标确实令人烦恼，但通过直接操作底层的SQLite数据库，我们能够彻底清除这些&amp;quot;幽灵&amp;quot;图标。希望这个方法能帮助你解决这个问题！&lt;/p&gt; &lt;p&gt;如果你有其他Mac使用方面的问题，欢迎在评论区留言讨论。&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;注意：操作系统数据库存在风险，请在执行前确保理解命令的含义。对于不熟悉的用户，建议寻求专业技术支持。&lt;/em&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 27 Oct 2025 02:20:00 GMT</pubDate>
    </item>
    <item>
      <title>GIT  SSH Key 配置管理</title>
      <link>https://maruifu.cn/article/356</link>
      <content:encoded>&lt;h1&gt;公钥管理&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;以下通过gitea 来演示,gitee github 基本大同小异&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;用户可以通过仓库主页 「设置」-&amp;gt;「SSH /GPG密钥」 ，浏览/验证/删除当前仓库已添加的SSH Key。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/20/image-20250820090110102.png" alt="image-20250820090110102" title="image-20250820090110102" /&gt;&lt;/p&gt; &lt;h1&gt;生成/添加 SSH 公钥&lt;/h1&gt; &lt;p&gt;Gitea 提供了基于 SSH 协议的 Git 服务，在使用 SSH 协议访问仓库之前，需要先配置好账户/仓库的 SSH 公钥。&lt;/p&gt; &lt;p&gt;你可以按如下命令来生成 sshkey:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;ssh-keygen -t ed25519 -C &amp;quot;xxxxx@xxxxx.com&amp;quot;   # Generating public/private ed25519 key pair... &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意：这里的 &lt;a href="mailto:xxxxx@xxxxx.com" target="_blank"&gt;xxxxx@xxxxx.com&lt;/a&gt; 只是生成的 sshkey 的名称，并不约束或要求具体命名为某个邮箱。 现网的大部分教程均讲解的使用邮箱生成，其一开始的初衷仅仅是为了便于辨识所以使用了邮箱。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;按照提示完成三次回车，即可生成 ssh key。通过查看 ~/.ssh/id_ed25519.pub 文件内容，获取到你的 public key&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;cat ~/.ssh/id_ed25519.pub # ssh-ed25519 AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc.... &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://help.gitee.com/assets/images/165113_8e58f0e1_551147-59f6a70c273a338dad20e5cb8945ac5e.webp" alt="输入图片说明" title="输入图片说明" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/20/165455_ec7dbd09_551147-afec57da5858ea02dae4935c382c4264.webp" alt="输入图片说明" title="输入图片说明" /&gt;&lt;/p&gt; &lt;p&gt;复制生成后的 ssh key，通过仓库主页「设置」-&amp;gt;「SSH /GPG密钥」-&amp;gt;「增加密钥」 ，添加生成的 public key 添加到仓库中。&lt;/p&gt; &lt;p&gt;添加后，在终端（Terminal）中输入&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;ssh -T git@maruifu.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;首次使用需要确认并添加主机到本机 SSH 可信列表。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maruifu@XMG-M4ProMax ~ % ssh -T git@maruifu.com  The authenticity of host '[maruifu.com] ([182.156.236.139])' can't be established. ED25519 key fingerprint is SHA256:m7seiLokJsJhj9yuyrSQ6ZoEtHgRvHcV3tYEPNE9cRI. This host key is known by the following other names/addresses:     ~/.ssh/known_hosts:14: [git.maruifu.com]:222 Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[maruifu.com]' (ED25519) to the list of known hosts. Hi there, maruifu! You've successfully authenticated with the key named marf@pep.com.cn, but Gitea does not provide shell access. If this is unexpected, please log in with password and setup Gitea under another user. &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;pre&gt;&lt;code&gt;ssh -T -p 2222 git@maruifu.com &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;ssh&lt;/code&gt;: Secure Shell 协议。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-T&lt;/code&gt;: 禁用伪终端（PTY）分配。对于只是测试连接或执行简单命令（如 Git 操作）来说，这很常用，因为它更简洁安全。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-p 2222&lt;/code&gt;: 指定连接远程主机的端口号为 &lt;code&gt;222&lt;/code&gt;（而不是默认的 &lt;code&gt;222&lt;/code&gt;）。22端口可以省略此参数&lt;/li&gt; &lt;li&gt;&lt;code&gt;git@maruifu.com&lt;/code&gt;: 使用 &lt;code&gt;git&lt;/code&gt; 用户连接到 &lt;code&gt;maruifu.com&lt;/code&gt; 主机。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;p&gt;添加成功后，就可以使用 SSH 协议对仓库进行操作了。&lt;/p&gt; &lt;h1&gt;Git 配置多个 SSH Key&lt;/h1&gt; &lt;h3&gt;背景&lt;/h3&gt; &lt;p&gt;同时使用两个 Gitea 帐号，需要为两个帐号配置不同的 SSH Key：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;帐号 A 用于公司；&lt;/li&gt; &lt;li&gt;帐号 B 用于个人。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;解决方法&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;生成帐号 A 的 SSH Key，并在帐号 A 的 Gitea 设置页面添加 SSH 公钥：&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;ssh-keygen -t ed25519 -C &amp;quot;Gitea User A&amp;quot; -f ~/.ssh/Gitea_user_a_ed25519 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="2"&gt; &lt;li&gt;生成帐号 B 的 SSH-Key，并在帐号 B 的 Gitea 设置页面添加 SSH 公钥：&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;ssh-keygen -t ed25519 -C &amp;quot;Gitea User B&amp;quot; -f ~/.ssh/Gitea_user_b_ed25519 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;创建或者修改文件 &lt;code&gt;~/.ssh/config&lt;/code&gt;，添加如下内容：&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;Host gt_a     User git     Hostname git@maruifu.com     Port 22     IdentityFile ~/.ssh/Gitea_user_a_ed25519 Host gt_b     User git     Hostname git@maruifu.com     Port 22     IdentityFile ~/.ssh/Gitea_user_b_ed25519 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="4"&gt; &lt;li&gt;用 ssh 命令分别测试两个 SSH Key：&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-text"&gt;$ ssh -T gt_a Hi Gitea User A! You've successfully authenticated, but git@maruifu.com does not provide shell access.  $ ssh -T gt_b Hi Gitea User B! You've successfully authenticated, but git@maruifu.com does not provide shell access. &lt;/code&gt;&lt;/pre&gt; &lt;ol start="5"&gt; &lt;li&gt;拉取代码：&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;将 &lt;code&gt;git@git@maruifu.com&lt;/code&gt; 替换为 SSH 配置文件中对应的 &lt;code&gt;Host&lt;/code&gt;，如原仓库 SSH 链接为：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-text"&gt;git@git@maruifu.com:owner/repo.git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用帐号 A 推拉仓库时，需要将连接修改为：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-text"&gt;gt_a:owner/repo.git &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 20 Aug 2025 01:16:16 GMT</pubDate>
    </item>
    <item>
      <title>群晖 部署KMS服务器激活Windows</title>
      <link>https://maruifu.cn/article/355</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;本文将从搭建KMS服务器、使用PowerShell(管理员)两部分介绍如何使用KMS激活Windows。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;搭建KMS服务器&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;一些网上的KMS服务器也是可以用的，普通用户可跳过此步骤。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;项目地址： luodaoyi/kms-server&lt;/p&gt; &lt;p&gt;使用Docker拉取镜像并映射1688端口启动即可。此处以群晖为例，打开Docker，点击注册表，搜索kms-server，双击下载latest版本镜像&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-20250817170458203.png" alt="image-20250817170458203" title="image-20250817170458203" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-13.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;下载好后点击左侧的映像，找到刚刚下载的kms-server，双击创建一个新的容器。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-14.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;选择高级设置 – 端口设置，修改映射端口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-15.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-16-1024x737.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;至此局域网本地的kms-server服务器便搭建完成。&lt;/p&gt; &lt;h2&gt;激活Windows&lt;/h2&gt; &lt;p&gt;首先，我们需要根据系统版本修改对应的密钥。&lt;a href="https://technet.microsoft.com/en-us/library/jj612867.aspx" target="_blank"&gt;点击这里查看对应版本密钥&lt;/a&gt;&lt;/p&gt; &lt;p&gt;此处以Windows 11专业版为例，通过查表可以找到对应的Windows 10专业版密钥如&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-17.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;右击开始，选择PowerShell(管理员)或终端(管理员)，输入：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;此处的密钥需根据自己系统版本自行替换，具体版本对应密钥请查表 如图即为成功安装产品密钥：&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-18.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;输入以下命令来配置KMS服务器：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;slmgr /skms KMS服务器在局域网中的IP地址 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;如端口未映射成1688，需要在IP地址后携带端口号&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;如图即为配置成功：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-19.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;执行如下命令来激活Windows：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;slmgr /ato &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如图即为激活成功：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-20.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/08/17/image-21.png" alt="img" title="img" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 17 Aug 2025 09:12:29 GMT</pubDate>
    </item>
    <item>
      <title>Docker Gitea 启用SSH服务修改端口之大坑</title>
      <link>https://maruifu.cn/article/354</link>
      <content:encoded>&lt;p&gt;修改配置文件 &lt;code&gt;/data/gitea/conf/app.ini&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[server] SSH_DOMAIN = git.maruifu.cn  # 与http访问的域名配置一样即可，只需写域名，无需http以及端口号等； DISABLE_SSH = false SSH_PORT = 222  # 预期的端口,此端口需要在Docker 映射,即外网连接SSH时的端口; SSH_LISTEN_PORT = 22 # 必须是22,不可修改,配置文件修改此端口无效 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 16 Aug 2025 18:18:00 GMT</pubDate>
    </item>
    <item>
      <title>macOS 上使用 Homebrew 安装和配置 frp 客户端</title>
      <link>https://maruifu.cn/article/353</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;macOS 上使用 Homebrew 安装和配置 frp 客户端 (frpc) 指南 frp (Fast Reverse Proxy) 是一款高性能的反向代理应用，常用于内网穿透。本文将介绍在 macOS 上使用 Homebrew 安装 frpc，并进行配置和管理。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;安装 frpc&lt;/h2&gt; &lt;p&gt;使用 Homebrew 安装（推荐） 确保已安装 Homebrew&lt;/p&gt; &lt;p&gt;终端中执行：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;brew install frpc &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装完成后，frpc 可执行文件通常位于：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;M1/M2 系列芯片：/opt/homebrew/bin/frpc Intel 版 mac：/usr/local/bin/frpc &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置 frpc&lt;/h2&gt; &lt;p&gt;看配置文件是否存在 不存在手动创建,存在则跳过本步骤&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 创建配置文件目录 sudo mkdir -p /opt/homebrew/etc/frp # 创建配置文件 sudo nano /opt/homebrew/etc/frp/frpc.toml &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# 公共配置 serverAddr = &amp;quot;X.X.X.X&amp;quot;   # 服务器IP地址 serverPort = 7000 auth.method = &amp;quot;token&amp;quot; auth.token = &amp;quot;XXXX&amp;quot;   # MAC-VNC服务转发 [[proxies]] name = &amp;quot;MAC-VNC&amp;quot; type = &amp;quot;tcp&amp;quot; localIP = &amp;quot;127.0.0.1&amp;quot; localPort = 5900            # 本地的端口 remotePort = 15900           # 服务器上的访问端口  &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意：不要在 serverAddr 前加 http:// 或 https://&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;启动和关闭 frpc&lt;/h2&gt; &lt;h4&gt;启动 frpc&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;/opt/homebrew/bin/frpc -c /opt/homebrew/etc/frp/frpc.toml  如果配置正确，将看到例如下面的输出： start proxy success &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;关闭 frpc&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;pkill frpc &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;设置开机自启动&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;使用 Homebrew Services&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;启动 frpc 服务&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;brew services start frpc &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;默认会使用 &lt;code&gt;/opt/homebrew/etc/frp/frpc.toml&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;停止 frpc 服务&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;brew services stop frpc &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看日志&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;tail -f /opt/homebrew/var/log/frpc.log &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 23 Jun 2025 08:51:37 GMT</pubDate>
    </item>
    <item>
      <title>打包本地电脑谷歌浏览器插件</title>
      <link>https://maruifu.cn/article/352</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;不同系统安装地址不一样&lt;/p&gt; &lt;/blockquote&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;系统&lt;/th&gt;&lt;th&gt;地址&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;MAC&lt;/td&gt;&lt;td&gt;~/Library/Application\ Support/Google/Chrome/Default/Extensions&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WINDOWS&lt;/td&gt;&lt;td&gt;C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data\Default\Extensions&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LINUX&lt;/td&gt;&lt;td&gt;~/.config/google-chrome/Default/Extensions&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;查看插件ID&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/06/23/image-20250623100245486.png" alt="image-20250623100245486" title="image-20250623100245486" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/06/23/image-20250623100525493.png" alt="image-20250623100525493" title="image-20250623100525493" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我的这个插件ID是  &lt;code&gt;dhdgffkkebhmkfjojejmpbldmpobfkfo&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;打包插件&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/06/23/image-20250623100953714.png" alt="image-20250623100953714" title="image-20250623100953714" /&gt;&lt;/p&gt; &lt;h2&gt;打包后当前目录会有一个后缀为CRX的文件&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/06/23/image-20250623101045139.png" alt="image-20250623101045139" title="image-20250623101045139" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 23 Jun 2025 02:17:29 GMT</pubDate>
    </item>
    <item>
      <title>群晖安装 Open VPN</title>
      <link>https://maruifu.cn/article/350</link>
      <content:encoded>&lt;h2&gt;适配机型&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518215549955.png" alt="image-20250518215549955" title="image-20250518215549955" /&gt;&lt;/p&gt; &lt;h2&gt;下载VPN Server套件&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;打开&lt;strong&gt;套件中心&lt;/strong&gt; ➔ 搜索&amp;quot;VPN Server&amp;quot; ➔ 安装&lt;/p&gt; &lt;p&gt;目前来说，这个方法好像不行了，原因嘛自行脑补。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;打开群晖官网&lt;/h3&gt; &lt;p&gt;https://www.synology.com/zh-tw/support/download/&lt;/p&gt; &lt;h3&gt;下载VPN Server&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518220519357.png" alt="image-20250518220519357" title="image-20250518220519357" /&gt;&lt;/p&gt; &lt;h2&gt;手动安装&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518220818009.png" alt="image-20250518220818009" title="image-20250518220818009" /&gt;&lt;/p&gt; &lt;h2&gt;配置 OpenVPN&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518221233853.png" alt="image-20250518221233853" title="image-20250518221233853" /&gt;&lt;/p&gt; &lt;h3&gt;修改配置文件&lt;/h3&gt; &lt;p&gt;导出配置文件后修改服务器地址&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518221949433.png" alt="image-20250518221949433" title="image-20250518221949433" /&gt;&lt;/p&gt; &lt;h2&gt;下载 OpenVPN 客户端&lt;/h2&gt; &lt;p&gt;访问官网下载  https://openvpn.net/client/ 客户端&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;打不开，可以下载上海大学的客户端  https://vpn.shu.edu.cn/index/OpenVPNsysm/MacOS.htm&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;客户端配置&lt;/h2&gt; &lt;h3&gt;导入配置文件&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;右键系统托盘图标 ➔ 选择&lt;strong&gt;导入文件&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;选择以下文件： 刚才修改后 VPNConfig.ovpn 文件&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;建立VPN连接&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;导入配置文件后，直接点“连接”；&lt;/li&gt; &lt;li&gt;根据提示，输入群晖上创建的、有权VPN访问的账号和密码；&lt;/li&gt; &lt;li&gt;查看连接状态：&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/18/image-20250518223008862.png" alt="image-20250518223008862" title="image-20250518223008862" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 18 May 2025 14:30:44 GMT</pubDate>
    </item>
    <item>
      <title>群晖系统百度网盘套件ContainerManager项目无法删除</title>
      <link>https://maruifu.cn/article/349</link>
      <content:encoded>&lt;h1&gt;&lt;/h1&gt; &lt;h2&gt;前言&lt;/h2&gt; &lt;p&gt;群晖系统重置了，群晖系统上面的应用都没了，但是 数据 包含docker 的数据都还在。我重新安装百度网盘的时候提示我&lt;/p&gt; &lt;p&gt;&lt;code&gt;name 'baiduapp' duplicated&lt;/code&gt;  名字重复，群晖的应用列表没有提示我这个重复。我想起这个应用 是 docker 项目&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/17/image-20250517002905950.png" alt="image-20250517002905950" title="image-20250517002905950" /&gt;&lt;/p&gt; &lt;p&gt;ContainerManager 里面确实有这个项目，但是是套件安装的也无法删除，现在就卡死在这里了，无法重新，也无法删除。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/17/image-20250517003201434.png" alt="image-20250517003201434" title="image-20250517003201434" /&gt;&lt;/p&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;h3&gt;开启 SHH&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/17/image-20250517003445077.png" alt="image-20250517003445077" title="image-20250517003445077" /&gt;&lt;/p&gt; &lt;h3&gt;SSH 操作删除&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 切换 root 用户 maruifu@XiaoMageNAS:~$ sudo -i  # 输入群晖密码 Password:          # 进入项目目录 root@XiaoMageNAS:~# cd /var/packages/ContainerManager/etc/projects  # 查看当前目录下的文件，我这里其实就一个59870233-d476-4c79-9c71-c990de8dc040 项目 ，其实直接删除这三个就行，如果有多个看下一步 root@XiaoMageNAS:/var/packages/ContainerManager/etc/projects# ls      59870233-d476-4c79-9c71-c990de8dc040.action.log  59870233-d476-4c79-9c71-c990de8dc040.config.json  59870233-d476-4c79-9c71-c990de8dc040.lock # 当前目录搜索 包含 baiduapp 的配置文件编号 root@XiaoMageNAS:/var/packages/ContainerManager/etc/projects# grep -rl --include=&amp;quot;*.json&amp;quot; &amp;quot;baiduapp&amp;quot; | awk -F/ '{print $NF}' | awk -F. '{print $1}' 59870233-d476-4c79-9c71-c990de8dc040 # 删除 所有  59870233-d476-4c79-9c71-c990de8dc040 开头的文件 root@XiaoMageNAS:/var/packages/ContainerManager/etc/projects# rm -rf 59870233-d476-4c79-9c71-c990de8dc040*  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;验证&lt;/h3&gt; &lt;p&gt;执行上面操作后删除了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/17/image-20250517004347941.png" alt="image-20250517004347941" title="image-20250517004347941" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 16 May 2025 17:11:00 GMT</pubDate>
    </item>
    <item>
      <title>部署 Docker 私服 docker-registry</title>
      <link>https://maruifu.cn/article/348</link>
      <content:encoded>&lt;h2&gt;部署docker-registry&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;networks:     1panel-network:         external: true services:     docker-registry:         container_name: ${CONTAINER_NAME}         deploy:             resources:                 limits:                     cpus: ${CPUS}                     memory: ${MEMORY_LIMIT}         environment:             - REGISTRY_HTTP_ADDR=0.0.0.0:15000             - REGISTRY_STORAGE_DELETE_ENABLED=true             - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain.pem             - REGISTRY_HTTP_TLS_KEY=/certs/privkey.pem         image: registry:3.0.0         labels:             createdBy: Apps         networks:             - 1panel-network         ports:             - ${HOST_IP}:${PANEL_APP_PORT_HTTP}:15000         restart: always         volumes:             - ./data:/var/lib/registry             - ./certs:/certs  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;部署docker-registry-ui&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;version: '3.8' networks:     1panel-network:         external: true services:   registry-ui:     image: joxit/docker-registry-ui:main     networks:       - 1panel-network     restart: always     ports:       - 15001:80     environment:       - SINGLE_REGISTRY=true       - REGISTRY_TITLE=Docker Registry UI       - DELETE_IMAGES=true       - SHOW_CONTENT_DIGEST=true       - NGINX_PROXY_PASS_URL=https://docker-registry:15000       - SHOW_CATALOG_NB_TAGS=true       - CATALOG_MIN_BRANCHES=1       - CATALOG_MAX_BRANCHES=1       - TAGLIST_PAGE_SIZE=100       - REGISTRY_SECURED=false       - CATALOG_ELEMENTS_LIMIT=1000     container_name: registry-ui &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;验证&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;sudo docker tag 1panel/maxkb:v1.10.7-lts s.mrf.ink:15000/1panel/maxkb:v1.10.7-lts sudo docker push s.mrf.ink:15000/1panel/maxkb:v1.10.7-lts &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 14 May 2025 15:49:00 GMT</pubDate>
    </item>
    <item>
      <title>1panel 容器部署  chevereto 开心版</title>
      <link>https://maruifu.cn/article/347</link>
      <content:encoded>&lt;h2&gt;下载容器&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;docker pull ghcr.io/chevereto/chevereto:4.0.9 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;开心版基于 4.0.7 开发的,我们下载 4.0.9 就可以&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;创建数据库&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/07/image-20250506160319247.png" alt="image-20250506160319247" title="image-20250506160319247" /&gt;&lt;/p&gt; &lt;h2&gt;运行容器&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;sudo docker run -d \   --name chevereto \   -p 9000:80 \               # 我这里使用的是9000端口,可以自定义修改   -e CHEVERETO_DB_HOST=192.168.2.1 \    # 使用数据库的IP   -e CHEVERETO_DB_USER=chevereto \       # 使用数据库用户   -e CHEVERETO_DB_PASS=password \        # 使用数据库的密码   -e CHEVERETO_DB_PORT=3306 \            # 使用数据库的端口   -e CHEVERETO_DB_NAME=chevereto \       # 使用数据库的实例   -e CHEVERETO_MAX_POST_SIZE=2G \   -e CHEVERETO_MAX_UPLOAD_SIZE=2G \   -e CHEVERETO_SERVICING=server \   -v /opt/chevereto/images:/var/www/html/images \   -v /opt/chevereto:/var/www/html \   ghcr.io/chevereto/chevereto:4.0.9 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;上传文件&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/07/image-20250506161531655.png" alt="image-20250506161531655" title="image-20250506161531655" /&gt;&lt;/p&gt; &lt;h2&gt;解压到当前文件夹&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/07/image-20250506161604048.png" alt="image-20250506161604048" title="image-20250506161604048" /&gt;&lt;/p&gt; &lt;h2&gt;修改上传后的文件权限&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/07/image-20250506161756152.png" alt="image-20250506161756152" title="image-20250506161756152" /&gt;&lt;/p&gt; &lt;h2&gt;访问网址&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/05/07/image-20250506161852554.png" alt="image-20250506161852554" title="image-20250506161852554" /&gt;&lt;/p&gt; &lt;h2&gt;其他&lt;/h2&gt; &lt;h3&gt;批量导入图片&lt;/h3&gt; &lt;h4&gt;创建文件夹(赋权限)&lt;/h4&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;./importing/&lt;/th&gt;&lt;th&gt;解析&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;parse-users/&lt;/td&gt;&lt;td&gt;&lt;a href="https://v4-admin.chevereto.com/dashboard/bulk-importer.html#top-level-folder-as-username" target="_blank"&gt;用户名作为顶级文件夹&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;parse-albums/&lt;/td&gt;&lt;td&gt;&lt;a href="https://v4-admin.chevereto.com/dashboard/bulk-importer.html#top-level-folders-as-albums" target="_blank"&gt;顶级文件夹作为专辑&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;no-parse/&lt;/td&gt;&lt;td&gt;&lt;a href="https://v4-admin.chevereto.com/dashboard/bulk-importer.html#no-parse" target="_blank"&gt;无需文件解析&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;执行批量导入&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;sudo docker exec -it --user www-data chevereto \     app/bin/legacy -C bulk-importer \     --privacy public &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;批量删除图片&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;-- 删除所有图片（谨慎使用！） DELETE FROM chv_images; -- 删除所有相册 DELETE FROM chv_albums; DELETE FROM chv_images_hash; -- 重置自增ID（可选） ALTER TABLE chv_images AUTO_INCREMENT = 1; ALTER TABLE chv_albums AUTO_INCREMENT = 1; ALTER TABLE chv_images_hash AUTO_INCREMENT = 1; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 07 May 2025 02:52:00 GMT</pubDate>
    </item>
    <item>
      <title>mac 安装HomeBrew</title>
      <link>https://maruifu.cn/article/346</link>
      <content:encoded>&lt;h3&gt;Homebrew是什么？&lt;/h3&gt; &lt;p&gt;homebrew是一款Mac  OS平台下的软件包管理工具，拥有安装、卸载、更新、查看、搜索等功能。通过简单的指令可以实现包管理，而不用关心各种依赖和文件路径情况。&lt;/p&gt; &lt;h3&gt;安装指令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;/bin/zsh -c &amp;quot;$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装过程&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;maruifu@marfMac-mini tools % /bin/zsh -c &amp;quot;$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)&amp;quot;  git version 2.39.2 (Apple Git-143)                 开始执行Homebrew自动安装程序              [cunkai.wang@foxmail.com]           ['2025-04-27 22:46:35']['13.7']        https://zhuanlan.zhihu.com/p/111014448   请选择下列一个 数字编号 后回车 （这里只是下载brew，随意选。国内下载源有5种稍后让你选择配置）  1、通过清华大学下载brew 2、通过Gitee下载brew 3、！我已经安装brew，跳过克隆，直接带我去配置国内下载源 4、不克隆brew，只把仓库地址设置成Gitee 5、不克隆brew，只把仓库地址设置成清华大学  请输入序号: 2       你选择了Gitee brew本体下载源   Mac os设置开机密码方法：     (设置开机密码：在左上角苹果图标-&amp;gt;系统偏好设置-&amp;gt;用户与群组-&amp;gt;更改密码)     (如果提示This incident will be reported. 在用户与群组中查看是否管理员) 请输入开机密码，输入过程不显示，输入完后回车 Password: 已获取权限      ==&amp;gt; 安装过程开始调用Brew官方安装脚本，提示会变成英文，看不懂的复制到在线翻译。   如果下载速度慢可以ctrl+c或control+c重新运行脚本选择下载源    -&amp;gt;  !!!!是否删除之前本机安装的Brew（是Y  否N） 我没有检测本机是否安装brew，选哪个都会继续运行   (Y/N):   Y  --&amp;gt; 脚本开始执行   ---备份要删除的/usr/local/Homebrew到系统桌面，后续可把桌面的文件删除....    ---/usr/local/Homebrew 备份完成   ---备份要删除的/Users/maruifu/Library/Caches/Homebrew到系统桌面，后续可把桌面的文件删除....    ---/Users/maruifu/Library/Caches/Homebrew 备份完成 未发现Git代理（属于正常状态）                 开始  进入brew官方安装脚本  开始   下载官方install.sh文件,当前目录是: /Users/maruifu Cloning into 'brew-install-ck'... remote: Enumerating objects: 21, done. remote: Counting objects: 100% (21/21), done. remote: Compressing objects: 100% (19/19), done. remote: Total 21 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0) Receiving objects: 100% (21/21), 22.17 KiB | 3.69 MiB/s, done. ==&amp;gt; Checking for `sudo` access (which may request your password)... ==&amp;gt; This script will install: /usr/local/bin/brew /usr/local/share/doc/homebrew /usr/local/share/man/man1/brew.1 /usr/local/share/zsh/site-functions/_brew /usr/local/etc/bash_completion.d/brew /usr/local/Homebrew  Press RETURN/ENTER 现在是brew官方安装提示，它需要你按回车键开始 other key to abort: ==&amp;gt; /usr/bin/sudo /bin/mkdir -p /usr/local/Homebrew ==&amp;gt; /usr/bin/sudo /usr/sbin/chown -R maruifu:admin /usr/local/Homebrew ==&amp;gt; /usr/bin/sudo /bin/mkdir -p /Users/maruifu/Library/Caches/Homebrew ==&amp;gt; /usr/bin/sudo /bin/chmod g+rwx /Users/maruifu/Library/Caches/Homebrew ==&amp;gt; /usr/bin/sudo /usr/sbin/chown -R maruifu /Users/maruifu/Library/Caches/Homebrew ==&amp;gt; Downloading and installing Homebrew... remote: Enumerating objects: 300043, done. remote: Counting objects: 100% (300043/300043), done. remote: Compressing objects: 100% (81201/81201), done. remote: Total 300043 (delta 215907), reused 291788 (delta 210079), pack-reused 0 (from 0) remote: Enumerating objects: 55, done. remote: Counting objects: 100% (34/34), done. remote: Total 55 (delta 34), reused 34 (delta 34), pack-reused 21 (from 1) ==&amp;gt; Fetching /usr/local/Homebrew...  ==&amp;gt; Resetting /usr/local/Homebrew... Reset branch 'stable'  ==&amp;gt; Installation successful!  ==&amp;gt; Homebrew has enabled anonymous aggregate formulae and cask analytics. Read the analytics documentation (and how to opt-out) here:   https://docs.brew.sh/Analytics No analytics data has been sent yet (nor will any be during this install run).  ==&amp;gt; Homebrew is run entirely by unpaid volunteers. Please consider donating:   https://gitee.com/Homebrew2/brew#donations  ==&amp;gt; Next steps: - Run these commands in your terminal to add Homebrew to your PATH:     echo &amp;gt;&amp;gt; /Users/maruifu/.zprofile     echo 'eval &amp;quot;$(/usr/local/bin/brew shellenv)&amp;quot;' &amp;gt;&amp;gt; /Users/maruifu/.zprofile     eval &amp;quot;$(/usr/local/bin/brew shellenv)&amp;quot; - Run brew help to get started - Further documentation:     https://docs.brew.sh  此步骤成功 Password:                    完成  退出brew官方安装脚本  完成    ==&amp;gt; 配置国内镜像源HOMEBREW BOTTLE 此处如果显示Password表示需要再次输入开机密码，输入完后回车 sed: /Users/maruifu/.zprofile: No such file or directory 有些电脑xcode和git混乱，再运行一次，此处如果有error正常。 xcode-select: error: command line tools are already installed, use &amp;quot;Software Update&amp;quot; in System Settings to install updates           Homebrew已经安装成功，接下来配置国内软件下载源。  请选择今后brew install的时候访问那个国内镜像，例如阿里巴巴，输入5回车。  1、中科大国内源 2、清华大学国内源 3、上海交通大学国内源 4、腾讯国内源 5、阿里巴巴国内源(推荐) 请输入序号: 5       你选择了阿里巴巴国内源            环境变量写入-&amp;gt;/Users/maruifu/.zprofile   此步骤成功  ==&amp;gt; 安装完成，brew版本  Homebrew 4.4.32 Homebrew前期配置成功 ==&amp;gt; Updating Homebrew... ==&amp;gt; Downloading https://mirrors.aliyun.com/homebrew/homebrew-bottles/bottles-portable-ruby/portable-ruby-3.3.8.el_capitan.bottle.tar.gz ######################################################################### 100.0% ==&amp;gt; Pouring portable-ruby-3.3.8.el_capitan.bottle.tar.gz ==&amp;gt; Homebrew collects anonymous analytics. Read the analytics documentation (and how to opt-out) here:   https://docs.brew.sh/Analytics No analytics have been recorded yet (nor will be during this `brew` run).  ==&amp;gt; Homebrew is run entirely by unpaid volunteers. Please consider donating:   https://github.com/Homebrew/brew#donations  Already up-to-date.          Homebrew自动安装程序运行完成           国内地址已经配置完成    之前步骤选了删除本机brew的话，桌面多出一个Old_Homebrew文件夹，可以删除。                初步介绍几个brew命令 查看版本：brew -v  更新brew版本：brew update 查找：brew search python（其中python替换为要查找的关键字） 安装：brew install python（其中python替换为要安装的名称） 本地软件库列表：brew ls           欢迎右键点击下方地址-打开链接 点个赞吧          https://zhuanlan.zhihu.com/p/111014448          如果遇到问题可以右键下面地址查看常见错误解决办法         https://gitee.com/cunkai/HomebrewCN/blob/master/error.md          brew官方地址：https://brew.sh/zh-cn/   安装成功 但还需要重启终端 或者 运行 source /Users/maruifu/.zprofile   否则国内地址无法生效  maruifu@marfMac-mini tools % source /Users/maruifu/.zprofile maruifu@marfMac-mini tools % maruifu@marfMac-mini tools % brew -v Homebrew 4.4.32 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 27 Apr 2025 14:58:32 GMT</pubDate>
    </item>
    <item>
      <title>ruoyi  mysql切换 pgsql</title>
      <link>https://maruifu.cn/article/345</link>
      <content:encoded>&lt;h2&gt;修改 &lt;a href="/src/main/resources/application.yml" target="_blank"&gt;application.yml&lt;/a&gt;&lt;/h2&gt; &lt;h3&gt;分页插件&lt;/h3&gt; &lt;h4&gt;原文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# PageHelper分页插件 pagehelper:   helperDialect: mysql   supportMethodsArguments: true   params: count=countSql &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;新文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# PageHelper分页插件 pagehelper:   helperDialect: postgresql   supportMethodsArguments: true   params: count=countSql &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改 &lt;a href="/src/main/resources/application-druid.yml" target="_blank"&gt;application-druid.yml&lt;/a&gt;&lt;/h2&gt; &lt;h3&gt;数据源&lt;/h3&gt; &lt;h4&gt;原文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;datasource:     type: com.alibaba.druid.pool.DruidDataSource     driverClassName: com.mysql.cj.jdbc.Driver     druid:         # 主库数据源         master:             url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;zeroDateTimeBehavior=convertToNull&amp;amp;useSSL=true&amp;amp;serverTimezone=GMT%2B8             username: root             password: password &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;新文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;datasource:   type: com.alibaba.druid.pool.DruidDataSource   driverClassName: org.postgresql.Driver   druid:     # 主库数据源     master:       url: jdbc:postgresql://172.16.100.207:5432/pepResourceDb?useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;allowMultiQueries=true&amp;amp;serverTimezone=Asia/Shanghai       username: develop       password: Develop-RS_1212 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;链接检测&lt;/h3&gt; &lt;h4&gt;原文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;新文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 配置检测连接是否有效 validationQuery: SELECT version() &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改 &lt;a href="../0.0.1/pepr/pepr-admin/pom.xml" target="_blank"&gt;pom.xml&lt;/a&gt; 驱动依赖&lt;/h2&gt; &lt;h3&gt;原文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;         &amp;lt;!-- Mysql驱动包 --&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;com.mysql&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;mysql-connector-j&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;新文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;        &amp;lt;!-- Postgresql驱动包 --&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.postgresql&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;postgresql&amp;lt;/artifactId&amp;gt;             &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;         &amp;lt;/dependency&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改数据表文件&lt;/h2&gt; &lt;h3&gt;定时任务表&lt;/h3&gt; &lt;h4&gt;原文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS;  -- ---------------------------- -- 1、存储每一个已配置的 jobDetail 的详细信息 -- ---------------------------- create table QRTZ_JOB_DETAILS (     sched_name           varchar(120)    not null            comment '调度名称',     job_name             varchar(200)    not null            comment '任务名称',     job_group            varchar(200)    not null            comment '任务组名',     description          varchar(250)    null                comment '相关介绍',     job_class_name       varchar(250)    not null            comment '执行任务类名称',     is_durable           varchar(1)      not null            comment '是否持久化',     is_nonconcurrent     varchar(1)      not null            comment '是否并发',     is_update_data       varchar(1)      not null            comment '是否更新数据',     requests_recovery    varchar(1)      not null            comment '是否接受恢复执行',     job_data             blob            null                comment '存放持久化job对象',     primary key (sched_name, job_name, job_group) ) engine=innodb comment = '任务详细信息表';  -- ---------------------------- -- 2、 存储已配置的 Trigger 的信息 -- ---------------------------- create table QRTZ_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_name         varchar(200)    not null            comment '触发器的名字',     trigger_group        varchar(200)    not null            comment '触发器所属组的名字',     job_name             varchar(200)    not null            comment 'qrtz_job_details表job_name的外键',     job_group            varchar(200)    not null            comment 'qrtz_job_details表job_group的外键',     description          varchar(250)    null                comment '相关介绍',     next_fire_time       bigint(13)      null                comment '上一次触发时间（毫秒）',     prev_fire_time       bigint(13)      null                comment '下一次触发时间（默认为-1表示不触发）',     priority             integer         null                comment '优先级',     trigger_state        varchar(16)     not null            comment '触发器状态',     trigger_type         varchar(8)      not null            comment '触发器的类型',     start_time           bigint(13)      not null            comment '开始时间',     end_time             bigint(13)      null                comment '结束时间',     calendar_name        varchar(200)    null                comment '日程表名称',     misfire_instr        smallint(2)     null                comment '补偿执行的策略',     job_data             blob            null                comment '存放持久化job对象',     primary key (sched_name, trigger_name, trigger_group),     foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group) ) engine=innodb comment = '触发器详细信息表';  -- ---------------------------- -- 3、 存储简单的 Trigger，包括重复次数，间隔，以及已触发的次数 -- ---------------------------- create table QRTZ_SIMPLE_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     repeat_count         bigint(7)       not null            comment '重复的次数统计',     repeat_interval      bigint(12)      not null            comment '重复的间隔时间',     times_triggered      bigint(10)      not null            comment '已经触发的次数',     primary key (sched_name, trigger_name, trigger_group),     foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = '简单触发器的信息表';  -- ---------------------------- -- 4、 存储 Cron Trigger，包括 Cron 表达式和时区信息 -- ----------------------------  create table QRTZ_CRON_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     cron_expression      varchar(200)    not null            comment 'cron表达式',     time_zone_id         varchar(80)                         comment '时区',     primary key (sched_name, trigger_name, trigger_group),     foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = 'Cron类型的触发器表';  -- ---------------------------- -- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型，JobStore 并不知道如何存储实例的时候) -- ----------------------------  create table QRTZ_BLOB_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     blob_data            blob            null                comment '存放持久化Trigger对象',     primary key (sched_name, trigger_name, trigger_group),     foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = 'Blob类型的触发器表';  -- ---------------------------- -- 6、 以 Blob 类型存储存放日历信息， quartz可配置一个日历来指定一个时间范围 -- ----------------------------  create table QRTZ_CALENDARS (     sched_name           varchar(120)    not null            comment '调度名称',     calendar_name        varchar(200)    not null            comment '日历名称',     calendar             blob            not null            comment '存放持久化calendar对象',     primary key (sched_name, calendar_name) ) engine=innodb comment = '日历信息表';  -- ---------------------------- -- 7、 存储已暂停的 Trigger 组的信息 -- ----------------------------  create table QRTZ_PAUSED_TRIGGER_GRPS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     primary key (sched_name, trigger_group) ) engine=innodb comment = '暂停的触发器表';  -- ---------------------------- -- 8、 存储与已触发的 Trigger 相关的状态信息，以及相联 Job 的执行信息 -- ----------------------------  create table QRTZ_FIRED_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     entry_id             varchar(95)     not null            comment '调度器实例id',     trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     instance_name        varchar(200)    not null            comment '调度器实例名',     fired_time           bigint(13)      not null            comment '触发的时间',     sched_time           bigint(13)      not null            comment '定时器制定的时间',     priority             integer         not null            comment '优先级',     state                varchar(16)     not null            comment '状态',     job_name             varchar(200)    null                comment '任务名称',     job_group            varchar(200)    null                comment '任务组名',     is_nonconcurrent     varchar(1)      null                comment '是否并发',     requests_recovery    varchar(1)      null                comment '是否接受恢复执行',     primary key (sched_name, entry_id) ) engine=innodb comment = '已触发的触发器表';  -- ---------------------------- -- 9、 存储少量的有关 Scheduler 的状态信息，假如是用于集群中，可以看到其他的 Scheduler 实例 -- ----------------------------  create table QRTZ_SCHEDULER_STATE (     sched_name           varchar(120)    not null            comment '调度名称',     instance_name        varchar(200)    not null            comment '实例名称',     last_checkin_time    bigint(13)      not null            comment '上次检查时间',     checkin_interval     bigint(13)      not null            comment '检查间隔时间',     primary key (sched_name, instance_name) ) engine=innodb comment = '调度器状态表';  -- ---------------------------- -- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) -- ----------------------------  create table QRTZ_LOCKS (     sched_name           varchar(120)    not null            comment '调度名称',     lock_name            varchar(40)     not null            comment '悲观锁名称',     primary key (sched_name, lock_name) ) engine=innodb comment = '存储的悲观锁信息表';  -- ---------------------------- -- 11、 Quartz集群实现同步机制的行锁表 -- ----------------------------  create table QRTZ_SIMPROP_TRIGGERS (     sched_name           varchar(120)    not null            comment '调度名称',     trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',     trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',     str_prop_1           varchar(512)    null                comment 'String类型的trigger的第一个参数',     str_prop_2           varchar(512)    null                comment 'String类型的trigger的第二个参数',     str_prop_3           varchar(512)    null                comment 'String类型的trigger的第三个参数',     int_prop_1           int             null                comment 'int类型的trigger的第一个参数',     int_prop_2           int             null                comment 'int类型的trigger的第二个参数',     long_prop_1          bigint          null                comment 'long类型的trigger的第一个参数',     long_prop_2          bigint          null                comment 'long类型的trigger的第二个参数',     dec_prop_1           numeric(13,4)   null                comment 'decimal类型的trigger的第一个参数',     dec_prop_2           numeric(13,4)   null                comment 'decimal类型的trigger的第二个参数',     bool_prop_1          varchar(1)      null                comment 'Boolean类型的trigger的第一个参数',     bool_prop_2          varchar(1)      null                comment 'Boolean类型的trigger的第二个参数',     primary key (sched_name, trigger_name, trigger_group),     foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = '同步机制的行锁表';  commit; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;新文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;-- Thanks to Patrick Lightbody for submitting this... -- -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate  DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS;   -- ---------------------------- -- 1、任务详细信息表 -- ----------------------------  CREATE TABLE QRTZ_JOB_DETAILS (   SCHED_NAME        VARCHAR(120) NOT NULL,   JOB_NAME          VARCHAR(200) NOT NULL,   JOB_GROUP         VARCHAR(200) NOT NULL,   DESCRIPTION       VARCHAR(250) NULL,   JOB_CLASS_NAME    VARCHAR(250) NOT NULL,   IS_DURABLE        BOOL         NOT NULL,   IS_NONCONCURRENT  BOOL         NOT NULL,   IS_UPDATE_DATA    BOOL         NOT NULL,   REQUESTS_RECOVERY BOOL         NOT NULL,   JOB_DATA          BYTEA        NULL,   PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) );  COMMENT ON TABLE QRTZ_JOB_DETAILS IS '任务详细信息表'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_name IS '任务名称'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_group IS '任务组名'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.description IS '相关介绍'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_class_name IS '执行任务类名称'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_durable IS '是否持久化'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_nonconcurrent IS '是否并发'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.is_update_data IS '是否更新数据'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.requests_recovery IS '是否接受恢复执行'; COMMENT ON COLUMN QRTZ_JOB_DETAILS.job_data IS '存放持久化job对象';   -- ---------------------------- -- 2、触发器详细信息表 -- ----------------------------  CREATE TABLE QRTZ_TRIGGERS (   SCHED_NAME     VARCHAR(120) NOT NULL,   TRIGGER_NAME   VARCHAR(200) NOT NULL,   TRIGGER_GROUP  VARCHAR(200) NOT NULL,   JOB_NAME       VARCHAR(200) NOT NULL,   JOB_GROUP      VARCHAR(200) NOT NULL,   DESCRIPTION    VARCHAR(250) NULL,   NEXT_FIRE_TIME BIGINT       NULL,   PREV_FIRE_TIME BIGINT       NULL,   PRIORITY       INTEGER      NULL,   TRIGGER_STATE  VARCHAR(16)  NOT NULL,   TRIGGER_TYPE   VARCHAR(8)   NOT NULL,   START_TIME     BIGINT       NOT NULL,   END_TIME       BIGINT       NULL,   CALENDAR_NAME  VARCHAR(200) NULL,   MISFIRE_INSTR  SMALLINT     NULL,   JOB_DATA       BYTEA        NULL,   PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),   FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)   REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ); COMMENT ON TABLE QRTZ_TRIGGERS IS '触发器详细信息表'; COMMENT ON COLUMN QRTZ_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_name IS '触发器的名字'; COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_group IS '触发器所属组的名字'; COMMENT ON COLUMN QRTZ_TRIGGERS.job_name IS 'qrtz_job_details表job_name的外键'; COMMENT ON COLUMN QRTZ_TRIGGERS.job_group IS 'qrtz_job_details表job_group的外键'; COMMENT ON COLUMN QRTZ_TRIGGERS.description IS '相关介绍'; COMMENT ON COLUMN QRTZ_TRIGGERS.next_fire_time IS '上一次触发时间（毫秒）'; COMMENT ON COLUMN QRTZ_TRIGGERS.prev_fire_time IS '下一次触发时间（默认为-1表示不触发）'; COMMENT ON COLUMN QRTZ_TRIGGERS.priority IS '优先级'; COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_state IS '触发器状态'; COMMENT ON COLUMN QRTZ_TRIGGERS.trigger_type IS '触发器的类型'; COMMENT ON COLUMN QRTZ_TRIGGERS.start_time IS '开始时间'; COMMENT ON COLUMN QRTZ_TRIGGERS.end_time IS '结束时间'; COMMENT ON COLUMN QRTZ_TRIGGERS.calendar_name IS '日程表名称'; COMMENT ON COLUMN QRTZ_TRIGGERS.misfire_instr IS '补偿执行的策略'; COMMENT ON COLUMN QRTZ_TRIGGERS.job_data IS '存放持久化job对象';    -- ---------------------------- -- 3、简单触发器的信息表 -- ----------------------------  CREATE TABLE QRTZ_SIMPLE_TRIGGERS (   SCHED_NAME      VARCHAR(120) NOT NULL,   TRIGGER_NAME    VARCHAR(200) NOT NULL,   TRIGGER_GROUP   VARCHAR(200) NOT NULL,   REPEAT_COUNT    BIGINT       NOT NULL,   REPEAT_INTERVAL BIGINT       NOT NULL,   TIMES_TRIGGERED BIGINT       NOT NULL,   PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),   FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)   REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) );  COMMENT ON TABLE QRTZ_SIMPLE_TRIGGERS IS '简单触发器的信息表'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.repeat_count IS '重复的次数统计'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.repeat_interval IS '重复的间隔时间'; COMMENT ON COLUMN QRTZ_SIMPLE_TRIGGERS.times_triggered IS '已经触发的次数';   -- ---------------------------- -- 4、Cron类型的触发器表 -- ----------------------------  CREATE TABLE QRTZ_CRON_TRIGGERS (   SCHED_NAME      VARCHAR(120) NOT NULL,   TRIGGER_NAME    VARCHAR(200) NOT NULL,   TRIGGER_GROUP   VARCHAR(200) NOT NULL,   CRON_EXPRESSION VARCHAR(120) NOT NULL,   TIME_ZONE_ID    VARCHAR(80),   PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),   FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)   REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) );   COMMENT ON TABLE QRTZ_CRON_TRIGGERS IS 'Cron类型的触发器表'; COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.cron_expression IS 'cron表达式'; COMMENT ON COLUMN QRTZ_CRON_TRIGGERS.time_zone_id IS '时区';    -- ---------------------------- -- 5、同步机制的行锁表 -- ---------------------------- CREATE TABLE QRTZ_SIMPROP_TRIGGERS (   SCHED_NAME    VARCHAR(120)   NOT NULL,   TRIGGER_NAME  VARCHAR(200)   NOT NULL,   TRIGGER_GROUP VARCHAR(200)   NOT NULL,   STR_PROP_1    VARCHAR(512)   NULL,   STR_PROP_2    VARCHAR(512)   NULL,   STR_PROP_3    VARCHAR(512)   NULL,   INT_PROP_1    INT            NULL,   INT_PROP_2    INT            NULL,   LONG_PROP_1   BIGINT         NULL,   LONG_PROP_2   BIGINT         NULL,   DEC_PROP_1    NUMERIC(13, 4) NULL,   DEC_PROP_2    NUMERIC(13, 4) NULL,   BOOL_PROP_1   BOOL           NULL,   BOOL_PROP_2   BOOL           NULL,   PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),   FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)   REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) );   COMMENT ON TABLE QRTZ_SIMPROP_TRIGGERS IS '同步机制的行锁表'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_1 IS 'String类型的trigger的第一个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_2 IS 'String类型的trigger的第二个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.str_prop_3 IS 'String类型的trigger的第三个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.int_prop_1 IS 'int类型的trigger的第一个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.int_prop_2 IS 'int类型的trigger的第二个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.long_prop_1 IS 'long类型的trigger的第一个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.long_prop_2 IS 'long类型的trigger的第二个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.dec_prop_1 IS 'decimal类型的trigger的第一个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.dec_prop_2 IS 'decimal类型的trigger的第二个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.bool_prop_1 IS 'Boolean类型的trigger的第一个参数'; COMMENT ON COLUMN QRTZ_SIMPROP_TRIGGERS.bool_prop_2 IS 'Boolean类型的trigger的第二个参数';  -- ---------------------------- -- 6、Blob类型的触发器表 -- ---------------------------- CREATE TABLE QRTZ_BLOB_TRIGGERS (   SCHED_NAME    VARCHAR(120) NOT NULL,   TRIGGER_NAME  VARCHAR(200) NOT NULL,   TRIGGER_GROUP VARCHAR(200) NOT NULL,   BLOB_DATA     BYTEA        NULL,   PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),   FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)   REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) );   COMMENT ON TABLE QRTZ_BLOB_TRIGGERS IS 'Blob类型的触发器表'; COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; COMMENT ON COLUMN QRTZ_BLOB_TRIGGERS.blob_data IS '存放持久化Trigger对象';  -- ---------------------------- -- 7、日历信息表 -- ---------------------------- CREATE TABLE QRTZ_CALENDARS (   SCHED_NAME    VARCHAR(120) NOT NULL,   CALENDAR_NAME VARCHAR(200) NOT NULL,   CALENDAR      BYTEA        NOT NULL,   PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) );  COMMENT ON TABLE QRTZ_CALENDARS IS '日历信息表'; COMMENT ON COLUMN QRTZ_CALENDARS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_CALENDARS.calendar_name IS '日历名称'; COMMENT ON COLUMN QRTZ_CALENDARS.calendar IS '存放持久化calendar对象';    -- ---------------------------- -- 8、暂停的触发器表 -- ---------------------------- CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (   SCHED_NAME    VARCHAR(120) NOT NULL,   TRIGGER_GROUP VARCHAR(200) NOT NULL,   PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) );    COMMENT ON TABLE QRTZ_PAUSED_TRIGGER_GRPS IS '暂停的触发器表'; COMMENT ON COLUMN QRTZ_PAUSED_TRIGGER_GRPS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_PAUSED_TRIGGER_GRPS.trigger_group IS 'qrtz_triggers表trigger_group的外键';  -- ---------------------------- -- 9、已触发的触发器表 -- ---------------------------- CREATE TABLE QRTZ_FIRED_TRIGGERS (   SCHED_NAME        VARCHAR(120) NOT NULL,   ENTRY_ID          VARCHAR(95)  NOT NULL,   TRIGGER_NAME      VARCHAR(200) NOT NULL,   TRIGGER_GROUP     VARCHAR(200) NOT NULL,   INSTANCE_NAME     VARCHAR(200) NOT NULL,   FIRED_TIME        BIGINT       NOT NULL,   SCHED_TIME        BIGINT       NOT NULL,   PRIORITY          INTEGER      NOT NULL,   STATE             VARCHAR(16)  NOT NULL,   JOB_NAME          VARCHAR(200) NULL,   JOB_GROUP         VARCHAR(200) NULL,   IS_NONCONCURRENT  BOOL         NULL,   REQUESTS_RECOVERY BOOL         NULL,   PRIMARY KEY (SCHED_NAME, ENTRY_ID) );   COMMENT ON TABLE QRTZ_FIRED_TRIGGERS IS '已触发的触发器表'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.entry_id IS '调度器实例id'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.trigger_name IS 'qrtz_triggers表trigger_name的外键'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.trigger_group IS 'qrtz_triggers表trigger_group的外键'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.instance_name IS '调度器实例名'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.fired_time IS '触发的时间'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.sched_time IS '定时器制定的时间'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.priority IS '优先级'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.state IS '状态'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.job_name IS '任务名称'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.job_group IS '任务组名'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.is_nonconcurrent IS '是否并发'; COMMENT ON COLUMN QRTZ_FIRED_TRIGGERS.requests_recovery IS '是否接受恢复执行';   -- ---------------------------- -- 10、调度器状态表 -- ---------------------------- CREATE TABLE QRTZ_SCHEDULER_STATE (   SCHED_NAME        VARCHAR(120) NOT NULL,   INSTANCE_NAME     VARCHAR(200) NOT NULL,   LAST_CHECKIN_TIME BIGINT       NOT NULL,   CHECKIN_INTERVAL  BIGINT       NOT NULL,   PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) );   COMMENT ON TABLE QRTZ_SCHEDULER_STATE IS '调度器状态表'; COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.instance_name IS '实例名称'; COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.last_checkin_time IS '上次检查时间'; COMMENT ON COLUMN QRTZ_SCHEDULER_STATE.checkin_interval IS '检查间隔时间';   -- ---------------------------- -- 11、存储的悲观锁信息表 -- ----------------------------  CREATE TABLE QRTZ_LOCKS (   SCHED_NAME VARCHAR(120) NOT NULL,   LOCK_NAME  VARCHAR(40)  NOT NULL,   PRIMARY KEY (SCHED_NAME, LOCK_NAME) );   COMMENT ON TABLE QRTZ_LOCKS IS '存储的悲观锁信息表'; COMMENT ON COLUMN QRTZ_LOCKS.sched_name IS '调度名称'; COMMENT ON COLUMN QRTZ_LOCKS.lock_name IS '悲观锁名称';   CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY   ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP   ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);  CREATE INDEX IDX_QRTZ_T_J   ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG   ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C   ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G   ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE   ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE   ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE   ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME   ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST   ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE   ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE   ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP   ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);  CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG   ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);   COMMIT;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;业务表&lt;/h3&gt; &lt;h3&gt;注释&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#去掉存储引擎和表注释  engine=innodb auto_increment=200 comment = 'XX表' #去掉字段注释 comment 'XX字段',  增加表注释 COMMENT ON TABLE table IS 'XX表'; COMMENT ON COLUMN table.field IS 'XX字段';  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;索引&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 删除 key idx_table_field (field), # 创建 CREATE INDEX idx_table_field ON table(field);  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;数据类型转换&lt;/h4&gt; &lt;blockquote&gt; &lt;pre&gt;&lt;code&gt;bigint(20)  not null auto_increment   --&amp;gt; BIGSERIAL NOT NULL bigint(20)                            --&amp;gt; BIGINT bigint(5)                            --&amp;gt; BIGINT int(4)                                --&amp;gt; int4 datetime                              --&amp;gt; TIMESTAMP tinyint(1)      default 1      --&amp;gt; bool DEFAULT TRUE int(1)  --&amp;gt; SMALLINT int(2)  --&amp;gt; SMALLINT &lt;/code&gt;&lt;/pre&gt; &lt;/blockquote&gt; &lt;h4&gt;数据sql文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 日期字段修改 sysdate()           --&amp;gt;                  NOW() # bool 字段对应的数据 1            --&amp;gt;                 true # 转义字符 \'            --&amp;gt;                ''  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;更新自增序列&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;DO $$ DECLARE     table_record  RECORD;     sequence_name TEXT;     max_value     BIGINT; BEGIN     -- 遍历所有表及其主键列     FOR table_record IN (         SELECT c.table_name, c.column_name         FROM information_schema.columns c         JOIN information_schema.key_column_usage k             ON c.table_name = k.table_name AND c.column_name = k.column_name         WHERE c.table_schema = 'public'           AND k.ordinal_position = 1           AND c.table_name NOT LIKE 'qrtz%'           AND c.table_name NOT IN ('sys_role_dept', 'sys_role_menu', 'sys_user_role')           AND c.data_type = 'bigint' -- 确保主键列是 bigint 类型     )     LOOP         -- 构建序列名称         sequence_name := table_record.table_name || '_' || table_record.column_name || '_seq';          -- 获取主键列的最大值         EXECUTE 'SELECT COALESCE(MAX(' || table_record.column_name || '), 0) FROM ' ||                 table_record.table_name INTO max_value;          -- 打印日志         RAISE NOTICE 'Updating sequence for table: %, column: %, max_value: %',                      table_record.table_name, table_record.column_name, max_value;          -- 更新序列值         EXECUTE 'SELECT setval(''public.' || sequence_name || ''', ' || (max_value + 1) || ', false)';     END LOOP; END $$; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;代码&lt;/h2&gt; &lt;h3&gt;全局搜XML替换&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;sysdate()  --&amp;gt;                  now() ifnull(   --&amp;gt;              COALESCE( m.status = 0   --&amp;gt;        m.status = '0'     `query`   --&amp;gt;        query &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改date_format方法&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# SysOperLogMapper.xml AND oper_time &amp;amp;gt;= #{params.beginTime}   ==&amp;gt; and oper_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd') AND oper_time &amp;amp;lt;= #{params.endTime}    ==&amp;gt;  and oper_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')  # SysUserMapper.xml AND date_format(u.create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  AND u.create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd')  AND date_format(u.create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt; AND u.create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')   # SysConfigMapper.xml and date_format(create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  and create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd')  and date_format(create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt;   and create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')  # GenTableMapper.xml  AND date_format(create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  and create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd') AND date_format(create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt;   and create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')  # SysJobLogMapper.xml and date_format(create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  and create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd') and date_format(create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt;   and create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')  #SysDictTypeMapper.xml and date_format(create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  and create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd') and date_format(create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt;   and create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')  #SysRoleMapper.xml and date_format(r.create_time,'%Y%m%d') &amp;amp;gt;= date_format(#{params.beginTime},'%Y%m%d') ==&amp;gt;  and r.create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd') and date_format(r.create_time,'%Y%m%d') &amp;amp;lt;= date_format(#{params.endTime},'%Y%m%d') ==&amp;gt;   and r.create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改find_in_set方法&lt;/h3&gt; &lt;p&gt;有3处&lt;/p&gt; &lt;pre&gt;&lt;code&gt;find_in_set(#{deptId}, ancestors)  cast( #{deptId} as VARCHAR) = ANY (string_to_array(ancestors,','))   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改 GenTableMapper.xml&lt;/h2&gt; &lt;p&gt;pgsql  information_schema.tables 不会存储表的创建时间或更新时间  所以关于根据时间查询的条件需要删除&lt;/p&gt; &lt;pre&gt;&lt;code&gt; SELECT table_name,  obj_description((table_schema || '.' || table_name)::regclass, 'pg_class') AS table_comment FROM information_schema.tables WHERE table_schema = current_schema(); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;selectDbTableList&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;select id=&amp;quot;selectDbTableList&amp;quot; parameterType=&amp;quot;GenTable&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   select table_name, table_comment, create_time, update_time from information_schema.tables   where table_schema = (select database())   AND table_name NOT LIKE 'qrtz\_%' AND table_name NOT LIKE 'gen\_%'   AND table_name NOT IN (select table_name from gen_table)   &amp;lt;if test=&amp;quot;tableName != null and tableName != ''&amp;quot;&amp;gt;    AND lower(table_name) like lower(concat('%', #{tableName}, '%'))   &amp;lt;/if&amp;gt;   &amp;lt;if test=&amp;quot;tableComment != null and tableComment != ''&amp;quot;&amp;gt;    AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))   &amp;lt;/if&amp;gt;   &amp;lt;if test=&amp;quot;params.beginTime != null and params.beginTime != ''&amp;quot;&amp;gt;&amp;lt;!-- 开始时间检索 --&amp;gt;    and create_time &amp;amp;gt;= to_date(#{params.beginTime},'yyyy-MM-dd')   &amp;lt;/if&amp;gt;   &amp;lt;if test=&amp;quot;params.endTime != null and params.endTime != ''&amp;quot;&amp;gt;&amp;lt;!-- 结束时间检索 --&amp;gt;    and create_time &amp;amp;lt;= to_date(#{params.endTime},'yyyy-MM-dd')   &amp;lt;/if&amp;gt;         order by create_time desc  &amp;lt;/select&amp;gt;   修改为   &amp;lt;select id=&amp;quot;selectDbTableList&amp;quot; parameterType=&amp;quot;GenTable&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   SELECT table_name,  obj_description((table_schema || '.' || table_name)::regclass, 'pg_class') AS table_comment   FROM information_schema.tables   WHERE table_schema = current_schema()   AND table_name NOT LIKE 'qrtz\_%' AND table_name NOT LIKE 'gen\_%'   AND table_name NOT IN (select table_name from gen_table)   &amp;lt;if test=&amp;quot;tableName != null and tableName != ''&amp;quot;&amp;gt;    AND lower(table_name) like lower(concat('%', #{tableName}, '%'))   &amp;lt;/if&amp;gt;   &amp;lt;if test=&amp;quot;tableComment != null and tableComment != ''&amp;quot;&amp;gt;    AND lower(obj_description((table_schema || '.' || table_name)::regclass, 'pg_class')) like lower(concat('%', #{tableComment}, '%'))   &amp;lt;/if&amp;gt;   &amp;lt;/select&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;selectDbTableListByNames&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;   &amp;lt;select id=&amp;quot;selectDbTableListByNames&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   select table_name, table_comment, create_time, update_time from information_schema.tables   where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = (select database())   and table_name in      &amp;lt;foreach collection=&amp;quot;array&amp;quot; item=&amp;quot;name&amp;quot; open=&amp;quot;(&amp;quot; separator=&amp;quot;,&amp;quot; close=&amp;quot;)&amp;quot;&amp;gt;     #{name}         &amp;lt;/foreach&amp;gt;  &amp;lt;/select&amp;gt;      修改为     &amp;lt;select id=&amp;quot;selectDbTableListByNames&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   SELECT table_name,  obj_description((table_schema || '.' || table_name)::regclass, 'pg_class') AS table_comment   FROM information_schema.tables   where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = current_schema()   and table_name in      &amp;lt;foreach collection=&amp;quot;array&amp;quot; item=&amp;quot;name&amp;quot; open=&amp;quot;(&amp;quot; separator=&amp;quot;,&amp;quot; close=&amp;quot;)&amp;quot;&amp;gt;     #{name}         &amp;lt;/foreach&amp;gt;  &amp;lt;/select&amp;gt;   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;selectTableByName&lt;/p&gt; &lt;pre&gt;&lt;code&gt; &amp;lt;select id=&amp;quot;selectTableByName&amp;quot; parameterType=&amp;quot;String&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   select table_name, table_comment, create_time, update_time from information_schema.tables   where table_comment &amp;lt;![CDATA[ &amp;lt;&amp;gt; ]]&amp;gt; '' and table_schema = (select database())   and table_name = #{tableName}  &amp;lt;/select&amp;gt;    修改为   &amp;lt;select id=&amp;quot;selectTableByName&amp;quot; parameterType=&amp;quot;String&amp;quot; resultMap=&amp;quot;GenTableResult&amp;quot;&amp;gt;   SELECT table_name,  obj_description((table_schema || '.' || table_name)::regclass, 'pg_class') AS table_comment   FROM information_schema.tables   where table_comment &amp;lt;![CDATA[ &amp;lt;&amp;gt; ]]&amp;gt; '' and table_schema = current_schema()   and table_name = #{tableName}  &amp;lt;/select&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改 &lt;a href="/src/main/resources/mapper/generator/GenTableColumnMapper.xml" target="_blank"&gt;GenTableColumnMapper.xml&lt;/a&gt;&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;    &amp;lt;select id=&amp;quot;selectDbTableColumnsByName&amp;quot; parameterType=&amp;quot;String&amp;quot; resultMap=&amp;quot;GenTableColumnResult&amp;quot;&amp;gt;   select column_name, (case when (is_nullable = 'no' &amp;lt;![CDATA[ &amp;amp;&amp;amp; ]]&amp;gt; column_key != 'PRI') then '1' else '0' end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type   from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})   order by ordinal_position  &amp;lt;/select&amp;gt;    修改为   &amp;lt;select id=&amp;quot;selectDbTableColumnsByName&amp;quot; parameterType=&amp;quot;String&amp;quot; resultMap=&amp;quot;GenTableColumnResult&amp;quot;&amp;gt;         SELECT             column_name,             CASE                 WHEN is_nullable = 'NO' AND NOT EXISTS (                     SELECT 1                     FROM information_schema.key_column_usage kcu                     WHERE kcu.table_schema = c.table_schema                       AND kcu.table_name = c.table_name                       AND kcu.column_name = c.column_name                 ) THEN '1'                 ELSE '0'                 END AS is_required,             CASE                 WHEN EXISTS (                     SELECT 1                     FROM information_schema.key_column_usage kcu                     WHERE kcu.table_schema = c.table_schema                       AND kcu.table_name = c.table_name                       AND kcu.column_name = c.column_name                 ) THEN '1'                 ELSE '0'                 END AS is_pk,             ordinal_position AS sort,             col_description((quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass, ordinal_position) AS column_comment,             CASE                 WHEN column_default LIKE 'nextval(%' THEN '1'                 ELSE '0'                 END AS is_increment,             data_type ||             CASE                 WHEN character_maximum_length IS NOT NULL THEN '(' || character_maximum_length || ')'                 WHEN numeric_precision IS NOT NULL THEN '(' || numeric_precision || ',' || numeric_scale || ')'                 ELSE ''                 END AS column_type         FROM             information_schema.columns c         WHERE             c.table_schema = current_schema()           AND c.table_name = (#{tableName})         ORDER BY             ordinal_position;  &amp;lt;/select&amp;gt;  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 26 Mar 2025 01:00:00 GMT</pubDate>
    </item>
    <item>
      <title>pip安装包－复制、安装到离线服务器里</title>
      <link>https://maruifu.cn/article/344</link>
      <content:encoded>&lt;p&gt;生成当前的依赖文件列表&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pip freeze &amp;gt;requirements.txt &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;下载requirements文件中的依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pip download -r requirements.txt -d packages/ &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;这里会新建一个packages的文件夹，把包的相关信息下载下来&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;复制packages/到对应离线服务器然后执行一下命令&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pip install --no-index --find-links=./packages -r ./requirements.txt &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;./packages是指定依赖包路径，./requirements.txt是指定依赖包列表路径&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;验证安装&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pip list &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 11 Mar 2025 03:39:31 GMT</pubDate>
    </item>
    <item>
      <title>python打包gradio 项目exe</title>
      <link>https://maruifu.cn/article/343</link>
      <content:encoded>&lt;h2&gt;安装打包工具&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pip install pyinstaller &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;进行打包&lt;/h2&gt; &lt;p&gt;使用命令 &lt;code&gt;pyinstaller -F app.py&lt;/code&gt; 进行打包，其输出如下所示&lt;/p&gt; &lt;pre&gt;&lt;code&gt;505731 INFO: Copying icon to EXE 505735 INFO: Copying 0 resources to EXE 505736 INFO: Embedding manifest in EXE 505784 INFO: Appending PKG archive to EXE 505838 INFO: Fixing EXE headers 519361 INFO: Building EXE from EXE-00.toc completed successfully. 520246 INFO: checking COLLECT 520246 INFO: Building COLLECT because COLLECT-00.toc is non existent 520246 INFO: Building COLLECT COLLECT-00.toc 542317 INFO: Building COLLECT COLLECT-00.toc completed successfully. &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;运行exe&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[Errno 2] No such file or directory: gradio_client\types.json &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是由于pyinstaller 没有准确的识别出用于代码中gradio_client与gradio库的依赖项，需要将打包命令修改下命令即可，也就是补充上 --collect-data=gradio_client --collect-data=gradio ,完整命令如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pyinstaller -F app.py --collect-data=gradio_client --collect-data=gradio &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行还是报错缺少其他文件,反复发现缺少好多依赖....&lt;/p&gt; &lt;h2&gt;查看当前所有依赖&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pip freeze &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;继续打包&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pyinstaller --onefile  --collect-all aiofiles --collect-all annotated_types --collect-all anyio --collect-all certifi --collect-all charset_normalizer --collect-all click --collect-all colorama --collect-all dateutil --collect-all fastapi --collect-all ffmpy --collect-all filelock --collect-all fsspec --collect-all gradio --collect-all gradio_client --collect-all groovy --collect-all h11 --collect-all httpcore --collect-all httpx --collect-all huggingface_hub --collect-all idna --collect-all jinja2 --collect-all lxml --collect-all markdown_it --collect-all markupsafe --collect-all mdurl --collect-all multipart --collect-all numpy --collect-all opencc --collect-all orjson --collect-all packaging --collect-all pandas --collect-all pillow --collect-all pip --collect-all pydantic --collect-all pydantic_core --collect-all pydub --collect-all pygments --collect-all python_multipart --collect-all pytz --collect-all requests --collect-all rich --collect-all ruff --collect-all safehttpx --collect-all semantic_version --collect-all setuptools --collect-all shellingham --collect-all sniffio --collect-all starlette --collect-all tomlkit --collect-all tqdm --collect-all typer --collect-all tzdata --collect-all urllib3 --collect-all uvicorn --collect-all websockets --collect-all wheel app.py &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再次执行时出现以下报错&lt;/p&gt; &lt;pre&gt;&lt;code&gt;FileNotFoundError: [Errno 2] No such file or directory: gradio\blocks_events.pyc &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是由于gradio库中的代码都是pyi文件，而pyinstaller 在打包时默认库中的都是pyc文件，故而需要修改spec文件，指定对gradio库下的代码进行编译。&lt;/p&gt; &lt;h2&gt;生成spec文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pyi-makespec --onefile  --collect-all aiofiles --collect-all annotated_types --collect-all anyio --collect-all certifi --collect-all charset_normalizer --collect-all click --collect-all colorama --collect-all dateutil --collect-all fastapi --collect-all ffmpy --collect-all filelock --collect-all fsspec --collect-all gradio --collect-all gradio_client --collect-all groovy --collect-all h11 --collect-all httpcore --collect-all httpx --collect-all huggingface_hub --collect-all idna --collect-all jinja2 --collect-all lxml --collect-all markdown_it --collect-all markupsafe --collect-all mdurl --collect-all multipart --collect-all numpy --collect-all opencc --collect-all orjson --collect-all packaging --collect-all pandas --collect-all pillow --collect-all pip --collect-all pydantic --collect-all pydantic_core --collect-all pydub --collect-all pygments --collect-all python_multipart --collect-all pytz --collect-all requests --collect-all rich --collect-all ruff --collect-all safehttpx --collect-all semantic_version --collect-all setuptools --collect-all shellingham --collect-all sniffio --collect-all starlette --collect-all tomlkit --collect-all tqdm --collect-all typer --collect-all tzdata --collect-all urllib3 --collect-all uvicorn --collect-all websockets --collect-all wheel app.py &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;隐藏控制台&lt;/h2&gt; &lt;p&gt;添加以下参数 &lt;code&gt;--noconsole&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pyi-makespec --onefile --noconsole --collect-all aiofiles --collect-all annotated_types --collect-all anyio --collect-all certifi --collect-all charset_normalizer --collect-all click --collect-all colorama --collect-all dateutil --collect-all fastapi --collect-all ffmpy --collect-all filelock --collect-all fsspec --collect-all gradio --collect-all gradio_client --collect-all groovy --collect-all h11 --collect-all httpcore --collect-all httpx --collect-all huggingface_hub --collect-all idna --collect-all jinja2 --collect-all lxml --collect-all markdown_it --collect-all markupsafe --collect-all mdurl --collect-all multipart --collect-all numpy --collect-all opencc --collect-all orjson --collect-all packaging --collect-all pandas --collect-all pillow --collect-all pip --collect-all pydantic --collect-all pydantic_core --collect-all pydub --collect-all pygments --collect-all python_multipart --collect-all pytz --collect-all requests --collect-all rich --collect-all ruff --collect-all safehttpx --collect-all semantic_version --collect-all setuptools --collect-all shellingham --collect-all sniffio --collect-all starlette --collect-all tomlkit --collect-all tqdm --collect-all typer --collect-all tzdata --collect-all urllib3 --collect-all uvicorn --collect-all websockets --collect-all wheel app.py &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改spec文件&lt;/h2&gt; &lt;p&gt;添加gradio的编译&lt;/p&gt; &lt;pre&gt;&lt;code&gt;a = Analysis(     ['app.spec'],     pathex=[],     binaries=binaries,     datas=datas,     hiddenimports=hiddenimports,     hookspath=[],     hooksconfig={},     runtime_hooks=[],     excludes=[],     noarchive=False,     module_collection_mode={ 'gradio': 'py',},     optimize=0, ) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改后，删除掉目录下的build文件夹，再次执行 &lt;code&gt;pyinstaller app.spec&lt;/code&gt; 即可&lt;/p&gt; &lt;h2&gt;成功运行&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;* Running on local URL:  http://10.211.55.5:7860  To create a public link, set `share=True` in `launch()`. &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 11 Mar 2025 03:20:00 GMT</pubDate>
    </item>
    <item>
      <title>minio如何配置防盗链</title>
      <link>https://maruifu.cn/article/342</link>
      <content:encoded>&lt;p&gt;MinIO 是一个开源的对象存储服务器，用于存储大量的数据，同时提供了丰富的功能和 API。配置防盗链可以帮助你控制谁可以访问存储在 MinIO 上的对象。以下是在 MinIO 中配置防盗链的一般步骤：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;编辑 &lt;code&gt;config.json&lt;/code&gt; 文件&lt;/strong&gt;： 找到 MinIO 服务器的配置文件 &lt;code&gt;config.json&lt;/code&gt;，通常位于 MinIO 数据目录中。在该文件中，你需要添加以下内容来启用和配置防盗链：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;{        &amp;quot;version&amp;quot;: &amp;quot;1&amp;quot;,        &amp;quot;s3&amp;quot;: {            &amp;quot;v4&amp;quot;: {                &amp;quot;domain&amp;quot;: &amp;quot;example.com&amp;quot;,                &amp;quot;website&amp;quot;: {                    &amp;quot;index_document_suffix&amp;quot;: &amp;quot;index.html&amp;quot;                },                &amp;quot;region&amp;quot;: &amp;quot;us-east-1&amp;quot;,                &amp;quot;signature_version&amp;quot;: &amp;quot;v4&amp;quot;,                &amp;quot;credentials&amp;quot;: {                    &amp;quot;access_key_id&amp;quot;: &amp;quot;YOUR_ACCESS_KEY&amp;quot;,                    &amp;quot;secret_access_key&amp;quot;: &amp;quot;YOUR_SECRET_KEY&amp;quot;                }            }        },        &amp;quot;policy&amp;quot;: {            &amp;quot;version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,            &amp;quot;statement&amp;quot;: [                {                    &amp;quot;effect&amp;quot;: &amp;quot;Allow&amp;quot;,                    &amp;quot;principal&amp;quot;: &amp;quot;*&amp;quot;,                    &amp;quot;action&amp;quot;: &amp;quot;s3:GetObject&amp;quot;,                    &amp;quot;resource&amp;quot;: &amp;quot;arn:aws:s3:::YOUR_BUCKET_NAME/*&amp;quot;,                    &amp;quot;condition&amp;quot;: {                        &amp;quot;StringLike&amp;quot;: {                            &amp;quot;aws:Referer&amp;quot;: [                                &amp;quot;http://your-allowed-domain.com/*&amp;quot;,                                &amp;quot;https://your-allowed-domain.com/*&amp;quot;                            ]                        }                    }                }            ]        }    } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;替换以下内容：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;example.com&lt;/code&gt;：你的 MinIO 服务器的域名。&lt;/li&gt; &lt;li&gt;&lt;code&gt;YOUR_ACCESS_KEY&lt;/code&gt; 和 &lt;code&gt;YOUR_SECRET_KEY&lt;/code&gt;：你的 MinIO 访问密钥。&lt;/li&gt; &lt;li&gt;&lt;code&gt;YOUR_BUCKET_NAME&lt;/code&gt;：你想要应用防盗链的存储桶名称。&lt;/li&gt; &lt;li&gt;&lt;code&gt;http://your-allowed-domain.com/*&lt;/code&gt; 和 &lt;code&gt;https://your-allowed-domain.com/*&lt;/code&gt;：允许访问的域名。你可以添加多个允许的域名。&lt;/li&gt; &lt;/ul&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;重启 MinIO 服务器&lt;/strong&gt;： 保存并关闭 &lt;code&gt;config.json&lt;/code&gt; 文件，然后重启 MinIO 服务器，以使更改生效。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;验证配置&lt;/strong&gt;： 确保只有来自允许的域名的请求才能成功获取对象。你可以使用不同的域名和工具来测试是否配置正确。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;通过这些步骤，你可以在 MinIO 中配置防盗链以增强数据安全性。&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 27 Feb 2025 03:17:00 GMT</pubDate>
    </item>
    <item>
      <title>nginx加速缓存导致event-stream消息延迟问题</title>
      <link>https://maruifu.cn/article/341</link>
      <content:encoded>&lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;在开发AI聊天助手功能模块，采用了跟chatGPT官网一样的stream流传输格式。&lt;/p&gt; &lt;p&gt;在建立起连接并且还未断的期间，后端服务器可以主动推送消息给前端。&lt;/p&gt; &lt;p&gt;在页面上能看到的效果就是AI回复的消息是一个字一个字打印出来的，而在浏览器的控制面板可以看到接口的response Content-Type是text/event-stream。&lt;/p&gt; &lt;p&gt;本地调试直接访问后端的地址没有问题,部署到线上,经过nginx后发现有问题.直接显示一段文字，然后卡顿两三秒，然后又是直接显示一段文字。整个过程看起来就很卡顿。&lt;/p&gt; &lt;p&gt;event-stream响应的数据几个chunk的发送时间一致,之前是每个chunk的发送时间都不一样.&lt;/p&gt; &lt;h2&gt;排查&lt;/h2&gt; &lt;p&gt;一开始还以为内外网的原因,因为一部署上去就走的外网,后来发现通过外网直接访问后台服务器地址也没有问题,才发现是nginx 缓存的事.&lt;/p&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;@PostMapping(value = &amp;quot;/chatStream&amp;quot; , produces = MediaType.TEXT_EVENT_STREAM_VALUE ) public Flux&amp;lt;ChatResponse&amp;gt;  chatStream(@RequestBody AiPrompt aiPrompt, HttpServletResponse response) {   //设置响应头   response.setHeader(&amp;quot;X-Accel-Buffering&amp;quot;, &amp;quot;no&amp;quot;); }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;设置&lt;code&gt;response.setHeader(&amp;quot;X-Accel-Buffering&amp;quot;, &amp;quot;no&amp;quot;);&lt;/code&gt;&lt;/p&gt; &lt;p&gt;用于控制 Nginx 的 &lt;strong&gt;X-Accel-Buffering&lt;/strong&gt; 行为的 HTTP 响应头。它通常用于禁用 Nginx 的响应缓冲功能，以便在java 中实现实时输出（例如流式传输或长轮询）。&lt;/p&gt; &lt;p&gt;后面再发送消息就没问题&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227110701210.png" alt="image-20250227110701210" title="image-20250227110701210" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 27 Feb 2025 03:07:27 GMT</pubDate>
    </item>
    <item>
      <title>官方下载 VMware并安装</title>
      <link>https://maruifu.cn/article/340</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;vmware被博通收购，官网越发的难下载...&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;软件下载地址&lt;/h2&gt; &lt;p&gt;官网：https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion&lt;/p&gt; &lt;p&gt;点击下载入口，跳转页面需要账号，没有账号可以走注册。正常按流程走注册即可&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227102159933.png" alt="image-20250227102159933" title="image-20250227102159933" /&gt;&lt;/p&gt; &lt;p&gt;登录进入，找到my downloads，点击下拉菜单，切换选项到三朵云VMware Cloud Foundation菜单项&lt;/p&gt; &lt;h3&gt;mac 版本&lt;/h3&gt; &lt;p&gt;如果是Mac 请下载 &lt;code&gt;VMware Fusion&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227102807154.png" alt="image-20250227102807154" title="image-20250227102807154" /&gt;&lt;/p&gt; &lt;h3&gt;Windows版本&lt;/h3&gt; &lt;p&gt;如果是Windows 请下载  &lt;code&gt;VMware Workstation Pro&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227103948651.png" alt="image-20250227103948651" title="image-20250227103948651" /&gt;&lt;/p&gt; &lt;h2&gt;下载指定版本&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227103109868.png" alt="image-20250227103109868" title="image-20250227103109868" /&gt;&lt;/p&gt; &lt;h2&gt;同意协议并下载&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227103218061.png" alt="image-20250227103218061" title="image-20250227103218061" /&gt;&lt;/p&gt; &lt;h2&gt;填写个人信息完成下载&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/27/image-20250227103434883.png" alt="image-20250227103434883" title="image-20250227103434883" /&gt;&lt;/p&gt; &lt;h2&gt;windows镜像下载&lt;/h2&gt; &lt;p&gt;官网下载镜像&lt;/p&gt; &lt;p&gt;https://www.microsoft.com/zh-cn/software-download/&lt;/p&gt; &lt;h2&gt;如何激活&lt;/h2&gt; &lt;h3&gt;win11安装过程中，可以选择先安装，后续输入产品序列号&lt;/h3&gt; &lt;p&gt;出现问题：输入激活序列号，还是无法激活成功&lt;/p&gt; &lt;pre&gt;&lt;code&gt;win+R powershell irm massgrave.dev/get.ps1 | iex &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 27 Feb 2025 02:47:41 GMT</pubDate>
    </item>
    <item>
      <title>中兴F50随身WiFi设置短信转发教程</title>
      <link>https://maruifu.cn/article/339</link>
      <content:encoded>&lt;p&gt;F50设备上通过ADB（Android Debug Bridge）进行投屏。以下是一些步骤和注意事项，帮助您顺利完成这个过程：&lt;/p&gt; &lt;h3&gt;前提条件&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;设备连接&lt;/strong&gt;：确保您的F50设备和电脑通过USB线连接。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;开启开发者选项&lt;/strong&gt;：在设备的设置中启用开发者选项，并打开USB调试。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;安装必要的驱动&lt;/strong&gt;：如果您是通过USB连接，确保电脑上已经安装了相应的USB驱动程序。&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;安装ADB工具&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 安装 brew install android-platform-tools # 查看版本 adb version &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;以上是mac安装,其他系统安装ADB工具，可以从&lt;a href="https://developer.android.com/studio/releases/platform-tools" target="_blank"&gt;Android官网&lt;/a&gt;下载并安装。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;开启USB端口&lt;/h3&gt; &lt;p&gt;​ 访问 http://192.168.0.1/ 这个地址通常是路由器管理界面的IP地址，请确认这是正确的地址并且您可以正常访问。&lt;/p&gt; &lt;p&gt;​        访问 http://192.168.0.1/index.html#usb_port 并打开 USB端口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/18/image-20250218142844355.png" alt="image-20250218142844355" title="image-20250218142844355" /&gt;&lt;/p&gt; &lt;h3&gt;安装投屏软件&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;选择投屏软件&lt;/strong&gt;：市面上有很多安卓投屏软件，如Scrcpy、Vysor等。以Scrcpy为例，您可以从其&lt;a href="https://github.com/Genymobile/scrcpy/releases/" target="_blank"&gt;GitHub页面&lt;/a&gt;下载并安装。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/18/image-20250218142935522.png" alt="image-20250218142935522" title="image-20250218142935522" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;启动投屏软件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;./scrcpy &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;启动失败可以 重启一下adb adb kill-server adb start-server&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/18/image-20250218143050527.png" alt="image-20250218143050527" title="image-20250218143050527" /&gt;&lt;/p&gt; &lt;h3&gt;开启ADB&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;进入开发者选项&lt;/strong&gt;： &lt;ul&gt; &lt;li&gt;打开设备的“设置”应用。&lt;/li&gt; &lt;li&gt;滚动到底部，找到并点击“关于手机”。&lt;/li&gt; &lt;li&gt;连续点击“版本号”或“构建号”，直到看到提示“您现在是开发者！”。&lt;/li&gt; &lt;li&gt;返回设置主菜单，现在应该可以看到“开发者选项”。&lt;/li&gt; &lt;li&gt;点击“开发者选项”，然后开启“USB调试”。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;使用ADB命令&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;连接设备&lt;/strong&gt;：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用USB线将设备连接到电脑，并确保设备已授权调试连接。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;验证连接&lt;/strong&gt;：输入以下命令来验证设备是否成功连接：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;adb devices &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果设备成功连接，应该会显示设备的序列号。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;开启ADB端口&lt;/strong&gt;：假设您需要通过ADB开启某个特定的功能或服务，可以使用以下命令：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;adb shell input keyevent KEYCODE_XXX &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中&lt;code&gt;KEYCODE_XXX&lt;/code&gt;是您需要模拟的按键代码。例如，开启USB调试模式：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;adb shell settings put global development_settings_enabled 1 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;安装短信转发器&lt;/p&gt; &lt;pre&gt;&lt;code&gt;adb install app/SmsF.apk &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;短信转发器下载地址  https://github.com/pppscn/SmsForwarder/releases&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;# 中兴F50 pro 开启ADB setprop sys.usb.config mtp,adb setprop sys.usb.state mtp,adb start adbd &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 18 Feb 2025 09:03:00 GMT</pubDate>
    </item>
    <item>
      <title>DeepSeek R1架构和训练过程图解</title>
      <link>https://maruifu.cn/article/338</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/deepseek-r1-architecture-and-training-process.png" alt="DeepSeek R1架构和训练过程图解" title="DeepSeek R1架构和训练过程图解" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;如果你对 AI 感兴趣，可能听说过 DeepSeek R1。它目前在 LLM 领域很流行，并且表现优于开源和闭源模型。&lt;/p&gt; &lt;p&gt;为了让一切变得简单，我们将使用手绘流程图和简单的计算来帮助从头开始澄清DeeoSeek-R1的核心概念。&lt;/p&gt; &lt;p&gt;事实上，我们将在整个文章中使用字符串 2 + 3 * 4 等于多少？作为示例，引导你了解 DeepSeek 技术报告的每个组成部分。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;1、快速概览&lt;/h2&gt; &lt;p&gt;因此，在介绍技术细节之前，快速概览一下：DeepSeek-R1 不是从头开始训练的，就像从无到有一样。相反，他们从一个非常聪明的 LLM 开始，他们已经有了 DeepSeek-V3，但他们想让它成为推理超级明星。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-182.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;为此，他们使用了强化学习（简称 RL），当 LLM 做出有利于推理的事情时，你会奖励它，否则会惩罚它。&lt;/p&gt; &lt;p&gt;但这不仅仅是一个简单的训练课程。它就像是一整套步骤，他们称之为管道。他们首先尝试了纯 RL，看看推理是否会自行出现，这就是 DeepSeek-R1-Zero，有点像一个实验。然后对于真正的 DeepSeek-R1，他们通过不同的阶段使其更有条理。他们给它一些起始数据来启动它，然后进行 RL，然后是更多数据，然后是更多 RL……就像一步一步升级一样！&lt;/p&gt; &lt;p&gt;整个重点是让这些语言模型更好地思考问题并为你提供明智的答案，而不仅仅是吐出单词。&lt;/p&gt; &lt;p&gt;所以，是的，在我们研究每个步骤的疯狂细节之前，这是超简短的版本。&lt;/p&gt; &lt;h2&gt;2、DeepSeek V3 (MOE) 如何思考？&lt;/h2&gt; &lt;p&gt;正如我之前所说，DeepSeek R1 训练不是从头开始构建的，而是使用 DeepSeek V3 作为基础模型。那么我们需要了解 V3 的工作原理以及它为什么被称为 MOE？&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-183.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;DeepSeek V3 有两条主要路径。当你输入问题时，它首先会经过一个记忆系统，该系统通过查找相关信息快速构建上下文。可以将其视为快速回忆你之前遇到过的类似情况。&lt;/p&gt; &lt;p&gt;它的主要优势在于其决策系统。在理解你的输入后，它会使用一个智能路由器在两条路径之间做出决定：快速处理器用于简单的任务（如简单问题或常见请求）和专家系统用于复杂的问题（如分析或专业知识）。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;这个路由器使 DeepSeek V3 成为专家混合模型 (MOE)&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;因为它会动态地将每个请求定向到最合适的专家组件以进行高效处理。&lt;/p&gt; &lt;p&gt;简单的问题通过快速路径获得快速、直接的答案，而复杂的查询则通过专家系统得到详细关注。最后，这些响应被组合成清晰、准确的输出。&lt;/p&gt; &lt;h2&gt;3、DeepSeek V3 作为 RL 设置中的策略模型&lt;/h2&gt; &lt;p&gt;现在我们已经了解了 DeepSeek v3 的思考方式，它是 DeepSeek R1 实现的起点，我所说的起点是指它已经创建了 DeepSeek R1 Zero版本，这是一个在创建最终版本之前存在一些错误的初始版本。&lt;/p&gt; &lt;p&gt;初始版本（R1 Zero）是使用强化学习创建的，其中 DeepSeek v3 充当 RL 代理（采取行动的参与者）。让我们首先直观地了解它的工作原理。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-184.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;RL 代理（DeepSeek V3）首先采取行动，这意味着它会为放入其环境中的给定问题生成答案和一些推理。在这种情况下，环境只是推理任务本身。&lt;/p&gt; &lt;p&gt;采取行动后，环境会给予奖励。这个奖励就像反馈，它告诉 DeepSeek V3 基础模型其行动有多好。积极的奖励意味着它做对了某件事，可能得到了正确的答案或推理得很好。然后，这个反馈信号返回到 DeepSeek-V3-Base，帮助它学习和调整未来如何采取行动以获得更好的奖励。&lt;/p&gt; &lt;p&gt;在接下来的部分中，我们将讨论这个带有奖励模型的 RL 设置以及它们使用的 RL 算法并尝试使用我们的文本输入来解决它。&lt;/p&gt; &lt;h2&gt;4、GRPO 算法如何工作？&lt;/h2&gt; &lt;p&gt;训练 LLM 的计算成本极高，而 RL 则增加了更多的复杂性。&lt;/p&gt; &lt;p&gt;因此，有许多可用的 RL 算法，但传统的 RL 使用一种称为“批评家”的东西来帮助主要决策部分（“参与者”，即 DeepSeek V3），正如你已经知道的那样。这个批评家通常和参与者本身一样大和复杂，这基本上使计算成本翻倍。&lt;/p&gt; &lt;p&gt;然而，GRPO 的做法不同，因为它直接从一组动作的结果中找出基线，即一种良好动作的参考点。因此，GRPO 根本不需要单独的批评家模型。这节省了大量计算并使事情变得更有效率。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-185.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;它从向模型提出一个问题或提示开始，称为“旧策略”。 GRPO 不会只得到一个答案，而是指示旧策略针对同一问题生成一组不同的答案。然后评估每个答案并给出奖励分数，以反映其好坏程度或可取性。&lt;/p&gt; &lt;p&gt;GRPO 通过将每个答案与其组中其他答案的平均质量进行比较来计算每个答案的“优势”。高于平均水平的答案获得正优势，而低于平均水平的答案获得负优势。至关重要的是，这无需单独的批评模型即可完成。&lt;/p&gt; &lt;p&gt;然后使用这些优势分数来更新旧策略，使其更有可能在未来产生高于平均水平的答案。这个更新的模型成为新的“旧策略”，这个过程重复进行，迭代地改进模型。&lt;/p&gt; &lt;h2&gt;5、GRPO 的目标函数&lt;/h2&gt; &lt;p&gt;显然，在这个 GRPO 背后，有复杂的数学💀总之，我们可以称之为 GRPO 背后的目标函数。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-186.png" alt="img" title="img" /&gt;GRPO 目标&lt;/p&gt; &lt;p&gt;GRPO 的目标函数有两个目标，一个是给出良好的输出（高回报），同时确保训练过程稳定且不会失控。原始函数很吓人，但我们会将其重写为更简单的形式，而不会失去其实际意义。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-187.png" alt="img" title="img" /&gt;目标函数（原始与简化）&lt;/p&gt; &lt;p&gt;让我们逐一分解。首先， &lt;code&gt;AverageResult[…]&lt;/code&gt; 或 &lt;code&gt;1/n[…]&lt;/code&gt; 指的是评估在许多不同情况下平均发生的情况。我们提出一个包含各种问题的模型。对于每个问题，模型都会生成一组答案。通过查看许多问题及其各自的答案组中的这些答案，我们可以计算出平均结果。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-188.png" alt="img" title="img" /&gt;平均的含义&lt;/p&gt; &lt;p&gt;在此过程中，问题被输入到旧模型中，该模型会产生多个答案（例如，答案 1、答案 2、...、答案 G）。这些答案形成一个组，通过评估不同问题中的这个组，我们得出平均结果。&lt;/p&gt; &lt;p&gt;&lt;code&gt;SumOf[..]&lt;/code&gt; 或 &lt;code&gt;∑[…]&lt;/code&gt; 指的是对一组答案（例如，答案 1、答案 2、...、答案 G）中的每个答案进行计算，然后将所有这些计算的结果加在一起。&lt;/p&gt; &lt;p&gt;然后是奖励部分。这是对给出良好答案的模型进行奖励的部分。它内部有点复杂，让我们放大一下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-189.png" alt="img" title="img" /&gt;公式&lt;/p&gt; &lt;p&gt;&lt;code&gt;ChangeRatio&lt;/code&gt; 告诉我们使用新模型给出此答案的几率是增加还是减少。具体来说，它着眼于：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用新模型的答案几率：新模型给出特定答案的可能性有多大。&lt;/li&gt; &lt;li&gt;使用旧模型的答案几率：旧模型给出相同答案的可能性有多大。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;接下来，优势 ( &lt;code&gt;Advantage&lt;/code&gt;) 分数表明与同一组中的其他答案相比，答案有多好或多差。它的计算方式如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-190.png" alt="img" title="img" /&gt;优势公式&lt;/p&gt; &lt;ul&gt; &lt;li&gt;答案的分数：给予特定答案的奖励。&lt;/li&gt; &lt;li&gt;组的平均分数：组中所有答案的平均奖励分数。&lt;/li&gt; &lt;li&gt;组中分数的分布：组中答案分数的差异有多大。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;Advantage&lt;/code&gt; 分数告诉我们答案是否优于该组内的平均水平，以及好多少。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-191.png" alt="img" title="img" /&gt;LimitedChangeRatio 公式（&lt;/p&gt; &lt;p&gt;&lt;code&gt;LimitedChangeRatio&lt;/code&gt; 是 &lt;code&gt;ChangeRatio&lt;/code&gt; 的修改版本。它确保 &lt;code&gt;ChangeRatio&lt;/code&gt; 不会波动太大，从而保持模型的学习稳定。限制由一个称为 &lt;code&gt;Epsilon&lt;/code&gt; 的小值决定， &lt;code&gt;h&lt;/code&gt; 确保变化不会太剧烈。&lt;/p&gt; &lt;p&gt;最后， &lt;code&gt;SmallerOf[ … ]&lt;/code&gt; 函数在两个选项中选择较小的值：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;ChangeRatio × Advantage&lt;/code&gt;：答案可能性的变化，乘以其优势分数。&lt;/li&gt; &lt;li&gt;&lt;code&gt;LimitedChangeRatio × Advantage&lt;/code&gt;：相同，但变化率有限。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;通过选择较小的值，模型可确保学习过程保持平稳，不会对性能的大幅变化反应过度。结果是“好答案奖励”，鼓励模型改进而不会过度补偿。&lt;/p&gt; &lt;p&gt;最后，我们减去 StayStablePart。这是为了防止新模型发生太大的变化。它不是很复杂，但让我们放大它：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-192.png" alt="img" title="img" /&gt;StayStable 方程&lt;/p&gt; &lt;p&gt;&lt;code&gt;DifferenceFromReferenceModel&lt;/code&gt; 测量新模型与参考模型（通常是旧模型）的差异。本质上，它有助于评估新模型与前一个模型相比所做的更改。&lt;/p&gt; &lt;p&gt;&lt;code&gt;Beta&lt;/code&gt; 值控制模型应保持与参考模型的接近程度。较大的 &lt;code&gt;Beta&lt;/code&gt; 意味着模型将优先保持更接近旧模型的行为和输出，以防止偏差太大。让我们将其可视化：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-193.png" alt="img" title="img" /&gt;StayStable 的视觉表示&lt;/p&gt; &lt;p&gt;简而言之， &lt;code&gt;StayStablePart&lt;/code&gt; 确保模型逐渐学习并且不会疯狂跳跃。&lt;/p&gt; &lt;h2&gt;6、DeepSeek R1 Zero 的奖励建模&lt;/h2&gt; &lt;p&gt;现在我们已经了解了主要的理论概念，让我们使用我们的文本输入来了解创建 R1 Zero 的奖励建模是如何工作的。&lt;/p&gt; &lt;p&gt;请记住，对于 R1 Zero，他们保持简单直接。他们没有使用花哨的神经网络来判断答案（就像他们在后期阶段可能会做的那样），而是使用了基于规则的奖励系统。&lt;/p&gt; &lt;p&gt;对于我们的数学问题：“2 + 3 * 4 等于多少？”&lt;/p&gt; &lt;h3&gt;6.1 基于规则的检查&lt;/h3&gt; &lt;p&gt;系统知道正确答案是 14。它将查看 DeepSeek V3（我们的 RL 代理）生成的输出，并专门检查  &lt;code&gt;&amp;lt;answer&amp;gt;&lt;/code&gt;标签内的内容。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-194.png" alt="img" title="img" /&gt;基于规则的检查&lt;/p&gt; &lt;p&gt;如果  &lt;code&gt;&amp;lt;answer&amp;gt;&lt;/code&gt;标签包含“14”（或数字相同的内容），它会得到正奖励，比如说 +1。如果它错了，它会得到 0 奖励，甚至可能是负奖励（尽管本文在这个阶段为了简单起见重点关注 0）。&lt;/p&gt; &lt;h3&gt;6.2 格式化奖励&lt;/h3&gt; &lt;p&gt;但 DeepSeek R1 Zero 还需要学习正确构建其推理，并且可以使用 &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;answer&amp;gt;&lt;/code&gt; 标签，正确设置格式的奖励较少。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-195.png" alt="img" title="img" /&gt;格式奖励过程&lt;/p&gt; &lt;p&gt;检查模型输出是否正确地将推理过程包含在 &lt;code&gt;&amp;lt;think&amp;gt; …&amp;lt;/think&amp;gt;&lt;/code&gt; 中，并将最终答案包含在 &lt;code&gt;&amp;lt;answer&amp;gt;… &amp;lt;/answer&amp;gt;&lt;/code&gt;中。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;DeepSeek R1 论文明确提到避免使用 DeepSeek-R1-Zero 的神经奖励模型，以防止奖励黑客攻击并降低初始探索阶段的复杂性&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;7、奖励训练模板&lt;/h2&gt; &lt;p&gt;为了使奖励模型有效，研究人员设计了一个特定的训练模板。该模板充当蓝图，指导 DeepSeek-V3-Base 如何在强化学习过程中构建其响应。&lt;/p&gt; &lt;p&gt;让我们看看原始模板并将其逐一分解：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;A conversation between User and Assistant. The user asks a question, and  the Assistant solves it. The assistant first thinks about the reasoning  process in the mind and then provides the user with the answer. The reasoning  process and answer are enclosed within &amp;lt;think&amp;gt; &amp;lt;/think&amp;gt; and &amp;lt;answer&amp;gt; &amp;lt;/answer&amp;gt; tags respectively, i.e., &amp;lt;think&amp;gt; reasoning process here &amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; answer here &amp;lt;/answer&amp;gt;. User: {prompt}. Assistant: &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;翻译： 用户和助手之间的对话。用户提出问题，助手解决该问题。助手首先在脑海中思考推理过程，然后为用户提供答案。推理过程和答案分别包含在  &lt;think&gt;&lt;/think&gt; 和 &lt;answer&gt;&lt;/answer&gt;  标签中，即  &lt;think&gt;推理过程在这里&lt;/think&gt;  &lt;answer&gt;答案在这里&lt;/answer&gt; 。用户：{prompt}。助手：&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;我们在 &lt;code&gt;{prompt}&lt;/code&gt; 中插入数学问题，例如 &lt;code&gt;2 + 3 * 4 等于多少？&lt;/code&gt;。重要的是那些  和  标签。这种结构化输出对于研究人员以后窥视模型的推理步骤非常重要。&lt;/p&gt; &lt;p&gt;当我们训练 DeepSeek-R1-Zero 时，我们使用此模板为其提供提示。对于我们的示例问题，输入将如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;A conversation between User and Assistant. The user asks a question, and  the Assistant solves it. The assistant first thinks about the reasoning  process in the mind and then provides the user with the answer. The reasoning  process and answer are enclosed within &amp;lt;think&amp;gt; &amp;lt;/think&amp;gt; and &amp;lt;answer&amp;gt; &amp;lt;/answer&amp;gt; tags respectively, i.e., &amp;lt;think&amp;gt; reasoning process here &amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; answer here &amp;lt;/answer&amp;gt;. User: What is 2 + 3 * 4?. Assistant: &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;翻译： 用户和助手之间的对话。用户提出问题，助手解决该问题。助手首先在脑海中思考推理过程，然后为用户提供答案。推理过程和答案分别包含在  &lt;think&gt;&lt;/think&gt; 和 &lt;answer&gt;&lt;/answer&gt;  标签中，即  &lt;think&gt;推理过程在这里&lt;/think&gt;  &lt;answer&gt;答案在这里&lt;/answer&gt; 。用户：2+3*4等于多少？。助手：&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;我们期望模型生成符合模板的输出，例如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt; Order of operations: multiply before add. 3 * 4 = 12. 2 + 12 = 14 &amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 14 &amp;lt;/answer&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;翻译： &lt;think&gt; 运算顺序：先乘后加。3 * 4 = 12。2 + 12 = 14 14 &lt;think&gt; &lt;answer&gt; 14 &lt;/answer&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;有趣的是，DeepSeek 团队有意保持这个模板简单并专注于结构，而不是告诉模型如何推理。&lt;/p&gt; &lt;h2&gt;8、DeepSeek R1 Zero 的强化学习训练过程&lt;/h2&gt; &lt;p&gt;尽管本文没有指定强化学习预训练的确切初始数据集，但我们假设它应该以推理为重点。&lt;/p&gt; &lt;p&gt;他们所做的第一步是使用旧策略（即强化学习更新之前的 DeepSeek-V3-Base 模型）生成多个可能的输出。在一次训练迭代中，我们假设 GRPO 采样一组 G = 4 个输出。&lt;/p&gt; &lt;p&gt;例如，该模型为我们的文本输入生成以下四个输出 2 + 3 * 4 等于多少？&lt;/p&gt; &lt;ul&gt; &lt;li&gt;o1：  &lt;code&gt;&amp;lt;think&amp;gt;2 + 3 = 5, 5 * 4 = 20&amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 20&amp;lt;/answer&amp;gt;&lt;/code&gt; （运算顺序不正确）&lt;/li&gt; &lt;li&gt;o2： &lt;code&gt;&amp;lt;think&amp;gt;3 * 4 = 12, 2 + 12 = 14&amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt;14&amp;lt;/answer&amp;gt;&lt;/code&gt; （正确）&lt;/li&gt; &lt;li&gt;o3： &lt;code&gt;&amp;lt;answer&amp;gt;14&amp;lt;/answer&amp;gt;&lt;/code&gt; （正确，但缺少 &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt;标签）&lt;/li&gt; &lt;li&gt;o4： &lt;code&gt;&amp;lt;think&amp;gt;...一些胡言乱语的推理...&amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 7&amp;lt;answer&amp;gt;&lt;/code&gt; （不正确且推理不佳）&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-196.png" alt="img" title="img" /&gt;生成输出&lt;/p&gt; &lt;p&gt;每个输出将根据正确性和推理质量进行评估并分配奖励。&lt;/p&gt; &lt;p&gt;为了引导模型进行更好的推理，基于规则的奖励系统应运而生。每个输出都根据以下条件分配奖励：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;准确度奖励：答案是否正确。&lt;/li&gt; &lt;li&gt;格式奖励：推理步骤是否使用  标签正确格式化。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;假设奖励分配如下：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;输出&lt;/th&gt;&lt;th&gt;准确率奖励&lt;/th&gt;&lt;th&gt;格式奖励&lt;/th&gt;&lt;th&gt;总奖励&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;o1（推理错误）&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;o2（推理正确）&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;td&gt;1.1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;o3（正确但缺少标签）&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;o4（推理错误且较差）&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-197.png" alt="img" title="img" /&gt;奖励细分&lt;/p&gt; &lt;p&gt;模型应该学会偏爱奖励更高的输出，同时降低生成不正确或不完整输出的概率。&lt;/p&gt; &lt;p&gt;为了确定每个输出对模型性能的改善或恶化程度，我们使用奖励值计算优势。优势有助于通过强化更好的输出来优化策略。&lt;/p&gt; &lt;p&gt;为此，让我们计算平均第一个奖励。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-198.png" alt="img" title="img" /&gt;平均奖励计算&lt;/p&gt; &lt;p&gt;标准差（近似值）= 0.5，现在计算每个输出的优势。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-199.png" alt="img" title="img" /&gt;计算每个输出的奖励&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-200.png" alt="img" title="img" /&gt;可视化优势计算&lt;/p&gt; &lt;p&gt;输出 o2 和 o3 获得正优势，这意味着应该鼓励它们。输出 o1 和 o4 获得负优势，这意味着应该阻止它们。&lt;/p&gt; &lt;p&gt;然后，GRPO 使用计算出的优势来更新策略模型 (DeepSeek-V3-Base)，以增加生成具有高优势的输出（如 o2 和 o3）的概率，并降低具有低优势或负优势的输出（如 o1 和 o4）的概率。&lt;/p&gt; &lt;p&gt;更新根据以下内容调整模型权重：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;策略比率：在新策略与旧策略下生成输出的概率。&lt;/li&gt; &lt;li&gt;裁剪机制：防止过大的更新，这可能会破坏训练的稳定性。&lt;/li&gt; &lt;li&gt;KL 发散惩罚：确保更新不会偏离原始模型太远。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-201.png" alt="img" title="img" /&gt;GRPO 工作&lt;/p&gt; &lt;p&gt;这确保在下一次迭代中，模型更有可能生成正确的推理步骤，同时减少不正确或不完整的响应。&lt;/p&gt; &lt;p&gt;因此，RL 是一个迭代过程。使用不同的推理问题重复上述步骤数千次。每次迭代都会逐渐提高模型的能力：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;执行正确的操作顺序&lt;/li&gt; &lt;li&gt;提供逻辑推理步骤&lt;/li&gt; &lt;li&gt;始终使用正确的格式&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;整体训练循环如下所示：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-202.png" alt="img" title="img" /&gt;DeepSeek 简化训练过程&lt;/p&gt; &lt;p&gt;随着时间的推移，模型会从错误中吸取教训，在解决推理问题方面变得更加准确和有效。 🚀&lt;/p&gt; &lt;h2&gt;9、R1 Zero 的两个主要问题&lt;/h2&gt; &lt;p&gt;在 V3 模型上使用 RL 训练过程创建 DeepSeek-R1 Zero 后，研究人员发现训练后的模型在推理测试中表现非常出色，甚至在 AIME 2024 等任务上的得分与 OpenAI-01-0912 等更高级的模型相似。这表明使用强化学习 (RL) 来鼓励语言模型中的推理是一种很有前途的方法。&lt;/p&gt; &lt;p&gt;但他们也注意到 DeepSeek-R1-Zero 有一些关键问题需要解决，才能在现实世界中使用并进行更广泛的研究。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-203.png" alt="img" title="img" /&gt;R1 Zero 的问题（&lt;/p&gt; &lt;p&gt;DeepSeek 的研究人员表示，该模板有意简单且结构集中。它避免对推理过程本身施加任何特定于内容的限制。例如，它没有说：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;“你必须使用分步推理”（它只是说“推理过程”，让模型来定义它的含义）。&lt;/li&gt; &lt;li&gt;“你必须使用反思性推理”&lt;/li&gt; &lt;li&gt;“你必须使用特定的问题解决策略”&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;主要问题是  标签内的推理过程难以阅读，使人类难以理解和分析。&lt;/p&gt; &lt;p&gt;另一个问题是语言混合，当被问到多语言问题时，模型有时会在同一个回答中混合使用多种语言，导致输出不一致和混乱。如果你用西班牙语问它问题。突然间，它的“思维”就会变成英语和西班牙语的混杂，不太完美！这些问题，混乱的推理和语言混乱，是明显的障碍。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;这是他们将最初的 R1 Zero 模型转变为 R1 的两个主要原因&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;在下一节中，我们将介绍他们如何将 R1 zero 模型改进为 R1 模型，从而提高其性能并帮助其胜过所有其他模型（无论是开源的还是封闭的）。&lt;/p&gt; &lt;h2&gt;10、冷启动数据&lt;/h2&gt; &lt;p&gt;因此，为了修复 R1 Zero 问题并真正让 DeepSeek 推理正确，研究人员进行了冷启动数据收集并包括监督微调。&lt;/p&gt; &lt;p&gt;你可以将其视为在真正激烈的 RL 训练之前为模型提供良好的推理基础。基本上，他们想教 DeepSeek-V3 Base 良好的推理是什么样子以及如何清晰地呈现它。&lt;/p&gt; &lt;h3&gt;10.1 使用长 CoT 进行少量提示&lt;/h3&gt; &lt;p&gt;他们为 DeepSeek-V3 Base 提供了一些问题示例以及非常详细的分步解决方案，称为思维链 (CoT)。这个想法是让模型通过示例学习并开始模仿这种分步推理风格。&lt;/p&gt; &lt;p&gt;让我们直观地了解这个基于示例的学习：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-204.png" alt="img" title="img" /&gt;基于示例的学习&lt;/p&gt; &lt;p&gt;对于我们的示例问题  &lt;code&gt;2 + 3 * 4 等于多少？&lt;/code&gt;，他们可能会显示如下提示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Problem Examples with Solutions:  Problem: What's the square root of 9 plus 5? Solution: | special_token | First, find the square root of 9, which is 3.  Then, add 5 to 3.  3 + 5 equals 8. | special_token | Summary: The answer is 8.  Problem: Train travels at 60 mph for 2 hours, how far? Solution: | special_token | Use the formula: Distance = Speed times Time.  Speed is 60 mph, Time is 2 hours. Distance = 60 * 2 = 120 miles.  | special_token | Summary: Train travels 120 miles.  Problem: What is 2 + 3 * 4? Solution: &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;那些 &lt;code&gt;| special_token |&lt;/code&gt; 东西只是将推理步骤与摘要分开的标记，使模型可以清楚地学习结构。&lt;/p&gt; &lt;p&gt;看到这些示例后，模型应该学会以类似的格式给出答案，例如 &lt;code&gt;2 + 3 * 4 等于多少？&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;| special_token |  Following order of operations (PEMDAS/BODMAS),  do multiplication before addition.  So, first calculate 3 * 4 = 12.  Then, add 2 to 12. 2 + 12 = 14. | special_token | Summary: The answer is 14. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;10.2 直接提示&lt;/h3&gt; &lt;p&gt;他们收集数据的另一种方法是直接提示模型不仅解决问题，而且还要逐步明确地展示其推理，然后仔细检查其答案。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-205.png" alt="img" title="img" /&gt;直接提示&lt;/p&gt; &lt;p&gt;这是为了促进更仔细和周到的问题解决。&lt;/p&gt; &lt;p&gt;对于 2 + 3 * 4 等于多少？，提示可能是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Problem: Solve this, show reasoning step-by-step, and verify: What is 2 + 3 * 4? &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;他们确实期望输出包含推理步骤和验证部分：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;| special_token | Reasoning: To solve 2 + 3 * 4, I need to use order of  operations. Multiplication comes before addition. Step 1: Calculate 3 * 4 = 12. Step 2: Add 2 to the result from step 1: 2 + 12 = 14. Verification:  Checking order of operations again, yes, multiplication  is before addition.  Calculation looks right. | special_token | Summary: The answer is 14. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;10.3 后处理细化&lt;/h3&gt; &lt;p&gt;他们甚至使用了已经训练过的 R1 Zero 模型的输出。尽管 R1 Zero 存在问题，但它可以进行一些推理。因此，他们采用了 R1 Zero 的输出，并让人类标注者使其更清晰、更结构化，并纠正任何错误。&lt;/p&gt; &lt;p&gt;例如，混乱的 R1 Zero 输出可能是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt;  ummm... multiply 3 and 4... get 12... then add 2...&amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 14 &amp;lt;/answer&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后人类会对其进行改进，使其更清晰、格式更好：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;| special_token | Reasoning: To solve this, we use order of operations, doing multiplication before addition. Step 1: Multiply 3 by 4, which is 12. Step 2: Add 2 to the result: 2 + 12 = 14. | special_token | Summary: The answer is 14. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可视化细化过程的工作原理如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-207.png" alt="img" title="img" /&gt;细化过程&lt;/p&gt; &lt;p&gt;他们最终获得的冷启动数据非常好，因为：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;高质量推理示例：每个示例都展示了良好的逐步推理。&lt;/li&gt; &lt;li&gt;一致、可读的格式： &lt;code&gt;| special_token |&lt;/code&gt; 格式使所有内容统一且易于处理。&lt;/li&gt; &lt;li&gt;人工检查：他们确保过滤掉任何不好的例子，因此数据干净可靠。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;获得此冷启动数据后，他们进行了监督微调 (SFT)。&lt;/p&gt; &lt;h2&gt;11、监督微调&lt;/h2&gt; &lt;p&gt;SFT 第 1 阶段的核心思想是使用监督学习来教 DeepSeek-V3-Base 如何产生高质量、结构化的推理输出。&lt;/p&gt; &lt;p&gt;基本上，我们向模型展示了许多良好推理的例子，并要求它学习模仿这种风格。&lt;/p&gt; &lt;p&gt;对于 SFT，我们需要将冷启动数据格式化为输入-目标对。对于数据集中的每个推理问题，我们都会创建一个这样的对：&lt;/p&gt; &lt;p&gt;输入 = 提示或问题描述本身&lt;/p&gt; &lt;pre&gt;&lt;code&gt;User: What is 2 + 3 * 4? Assistant: &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是我们输入到模型中的内容，我们的目标是相应的结构良好的推理和答案&lt;/p&gt; &lt;pre&gt;&lt;code&gt;| special_token | According to the order of operations (PEMDAS/BODMAS) ...  Summary: The answer is 14. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是我们希望模型学习生成的理想输出。&lt;/p&gt; &lt;p&gt;我们告诉模型：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;当你看到此输入（问题）时，我们希望你产生此目标输出（良好的推理和答案）&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;与其用详细的文字解释并让你难以理解，不如先将其可视化，以便更容易解释 SFT&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-208.png" alt="img" title="img" /&gt;SFT 流程&lt;/p&gt; &lt;p&gt;微调过程从输入开始：提示 + 目标推理，我们在此提供一个问题和一个结构化的推理示例。这会训练模型（DeepSeek-V3-Base 模型）以生成结构良好的响应。&lt;/p&gt; &lt;p&gt;在预测下一个标记中，模型会生成推理序列中的下一个单词。使用损失函数将其与比较目标标记（计算损失）中的实际下一个标记进行比较。损失越大，意味着预测距离正确标记越远。&lt;/p&gt; &lt;p&gt;在更新模型参数中，反向传播和优化器会调整模型的权重以&lt;/p&gt; &lt;p&gt;改进其预测。这个过程循环往复，重复许多输入目标对，每次迭代逐渐提高模型结构化推理能力。&lt;/p&gt; &lt;h2&gt;12、推理导向强化学习&lt;/h2&gt; &lt;p&gt;他们已经为 DeepSeek V3 提供了 SFT 推理教育，但为了真正提高其推理能力，研究人员引入了推理导向学习！&lt;/p&gt; &lt;p&gt;在这里，我们采用 SFT 微调的 DeepSeek-V3 模型，并通过强化学习推动它变得更好。&lt;/p&gt; &lt;p&gt;他们确实使用了相同的 GRPO 算法，但这一阶段真正的升级是奖励系统。他们添加了一些新的、非常重要的语言一致性奖励！&lt;/p&gt; &lt;p&gt;还记得 R1 Zero 有时会对语言感到困惑并开始混淆它们吗？为了解决这个问题，他们专门增加了保持语言一致性的奖励。这个想法很简单，如果你用英语问问题，我们希望推理和答案也是用英语。&lt;/p&gt; &lt;p&gt;让我们直观地了解一下这个语言一致性奖励计算：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-209.png" alt="img" title="img" /&gt;一致性奖励计算&lt;/p&gt; &lt;p&gt;为了理解上面的图表，让我们重新回顾之前的示例输出 o1 和 o2，看看奖励如何随着这个新的语言一致性奖励而变化。为简单起见，我们假设目标语言是英语。&lt;/p&gt; &lt;p&gt;让我们看看这些奖励如何与我们的示例输出一起发挥作用。考虑第一个输出 o1，它错误地计算了“2 + 3 * 4”，但用英语呈现了其有缺陷的推理：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt; 2 + 3 = 5, 5 * 4 = 20 &amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 20 &amp;lt;/answer&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;对于这个，准确度奖励自然是 0，因为答案是错误的。但是，由于假设推理 100% 使用目标语言（本例中为英语），因此它获得的语言一致性奖励为 1。&lt;/p&gt; &lt;p&gt;当我们计算 RL 阶段的总奖励时，我们会将这些结合起来。如果我们为准确度奖励分配权重 1，为语言一致性奖励分配较小的权重（例如 0.2），则 o1 的总奖励变为：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Total Reward = (1 * Accuracy Reward) + (0.2 * Language Consistency Reward)  (1 * 0) + (0.2 * 1) = 0.2 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在考虑输出 o2，它正确解决了问题并且还用英语进行推理：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt; 3 * 4 = 12, 2 + 12 = 14 &amp;lt;/think&amp;gt; &amp;lt;answer&amp;gt; 14 &amp;lt;/answer&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此输出因正确答案而获得完美的准确度奖励 1。假设它的推理也是 100% 英语，它也会获得 1 的语言一致性奖励。使用与之前相同的权重，o2 的总奖励为：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(1 * 1) + (0.2 * 1) = 1.2 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;请注意，语言一致性奖励如何略微提高正确答案的总奖励，甚至为错误答案 o1 提供小幅正奖励，只要它保持语言一致性。&lt;/p&gt; &lt;p&gt;此 RL 训练循环遵循我们之前看到的相同 DeepSeek R1 Zero 训练循环：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-210.png" alt="img" title="img" /&gt;推理导向循环&lt;/p&gt; &lt;ul&gt; &lt;li&gt;生成多个输出。&lt;/li&gt; &lt;li&gt;细化奖励，包括语言一致性。&lt;/li&gt; &lt;li&gt;使用 GRPO 进行优势估计。&lt;/li&gt; &lt;li&gt;训练模型以支持高优势输出。&lt;/li&gt; &lt;li&gt;重复该过程！&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;13、拒绝抽样&lt;/h2&gt; &lt;p&gt;对于推理数据，DeepSeek 团队希望获得绝对最佳示例以进一步训练模型。为此，他们使用了一种称为拒绝抽样的技术。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-211.png" alt="img" title="img" /&gt;拒绝抽样&lt;/p&gt; &lt;p&gt;为了改进推理数据，DeepSeek 使用了拒绝抽样。对于“2 + 3 * 4 等于多少？”，他们会从上一阶段模型生成许多输出。想象一下得到 20（错误）和 14 …（正确，推理）这样的输出。&lt;/p&gt; &lt;p&gt;然后他们会评估每个输出的正确性（答案“14”）和推理的可读性。只有正确且推理充分的最佳输出才会被保留，而其他输出则被拒绝。&lt;/p&gt; &lt;p&gt;对于复杂的推理，生成奖励模型用于判断推理质量。严格的过滤器会删除混合语言、漫无边际的推理或不相关的代码。此过程会产生约 600k 个高质量推理样本。&lt;/p&gt; &lt;p&gt;除了精炼推理数据外，他们还添加了非推理数据（约 20 万个样本），用于一般技能：写作、问答、翻译等，有时还会使用思维链来完成复杂任务。&lt;/p&gt; &lt;p&gt;最后，SFT 第 2 阶段使用下一个标记预测在组合数据集（精炼推理 + 非推理）上训练前一个模型检查点。此阶段使用来自拒绝采样的顶级示例进一步改进推理，并将模型推广到更广泛的任务，同时保持用户友好性。&lt;/p&gt; &lt;p&gt;“2 + 3 * 4 等于多少？”现在是一个完美精炼的推理示例，成为此训练数据的一部分。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;这是拒绝采样，我们拒绝低于标准的样本，只保留最好的样本以生成高质量的训练数据&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;14、适用于所有场景的 RL&lt;/h2&gt; &lt;p&gt;在 SFT 第 2 阶段之后，我们获得了 DeepSeek V3 推理、一致说话，甚至很好地处理了一般任务！但要真正使其成为顶级的人工智能助手，研究人员必须与人类价值观进行最后的调整。这就是强化学习在所有场景中的使命（强化学习第 2 阶段）！把它看作是让DeepSeek R1真正安全的最后一道工序。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-212.png" alt="img" title="img" /&gt;最终 RL 步骤&lt;/p&gt; &lt;p&gt;对于我们的示例“2 + 3 * 4 等于多少？”虽然准确度奖励仍然强化了正确答案，但奖励系统现在还考虑：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;有用性，评估摘要（如果生成）是否提供了除答案之外的有用背景。&lt;/li&gt; &lt;li&gt;无害性，检查整个输出是否安全且无偏见。这些通常由根据人类偏好训练的单独奖励模型进行评估。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;最终的奖励信号成为准确度、有用性和无害性分数的加权组合。&lt;/p&gt; &lt;p&gt;现在，训练数据包括&lt;/p&gt; &lt;ul&gt; &lt;li&gt;多样化组合，包括推理问题&lt;/li&gt; &lt;li&gt;一般 QA 提示&lt;/li&gt; &lt;li&gt;写作任务&lt;/li&gt; &lt;li&gt;和偏好对，其中人类指出两个模型输出中的哪一个在有用性和无害性方面更好。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;训练过程遵循迭代 RL 循环（可能使用 GRPO）以根据来自这些多样化数据的组合奖励信号优化模型。&lt;/p&gt; &lt;p&gt;经过多次训练迭代后，模型得到改进，在推理性能和一致性（有用性/无害性）之间取得良好平衡。一旦达到这种平衡，模型就会在流行的基准数据集上进行评估，并超越其他模型的性能。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;他们的最终检查点，高度优化的版本被命名为 DeepSeek-R1&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;15、蒸馏&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/11/image-213.png" alt="img" title="img" /&gt;R1 的蒸馏&lt;/p&gt; &lt;p&gt;在 DeepSeek 团队能够创建性能良好的 DeepSeek R1 后，他们进一步将更大的模型提炼为性能更高的小型模型，供社区使用，蒸馏过程的工作原理如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;数据准备：收集 800k 个推理样本。&lt;/li&gt; &lt;li&gt;DeepSeek-R1 输出：对于每个样本，教师模型（DeepSeek-R1）的输出用作学生模型的目标。&lt;/li&gt; &lt;li&gt;监督式微调 (SFT)：学生模型（例如 Qwen-1.5B、Llama-14B）基于这 800k 个样本进行微调，以匹配 DeepSeek-R1 输出。&lt;/li&gt; &lt;li&gt;蒸馏模型：学生模型现在被精炼成更小的版本，但保留了 DeepSeek-R1 的大部分推理能力。&lt;/li&gt; &lt;li&gt;结果：你将获得更小、更快且具有良好推理能力的模型，随时可以部署。&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;p&gt;原文链接：&lt;a href="https://levelup.gitconnected.com/drawing-deepseek-r1-architecture-and-training-process-from-scratch-72043da33955" target="_blank"&gt;Drawing DeepSeek R1 Architecture and Training Process from Scratch&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 11 Feb 2025 08:15:14 GMT</pubDate>
    </item>
    <item>
      <title>VMware ESXi 添加证书</title>
      <link>https://maruifu.cn/article/337</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;VMware ESXi都是强制使用HTTPS 去访问网页控制台的，浏览器就会疯狂提示安全性问题。这会带来非常多的不方便。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;网页导入&lt;/h2&gt; &lt;p&gt;通过主机管理界面-安全和用户-证书-导入证书PEM 然后点击保存后，恭喜你ESXi报错了。&lt;/p&gt; &lt;p&gt;因为ESXi的SSL证书只支持通过ESXi内置文件去申请SSL证书从而下载的证书PEM才可以导入进去。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/10/image-20250210100313176.png" alt="image-20250210100313176" title="image-20250210100313176" /&gt;&lt;/p&gt; &lt;h2&gt;手动上传&lt;/h2&gt; &lt;h3&gt;开启SSH&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/10/image-20250210101040547.png" alt="image-20250210101040547" title="image-20250210101040547" /&gt;&lt;/p&gt; &lt;h3&gt;进入上传目录&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;cd /etc/vmware/ssl/ &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;重命名&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 需要替换的对应关系如下： xxxxxx.key -&amp;gt; rui.key xxxxxx.crt -&amp;gt; rui.crt # 复制创建文件  vi rui.crt  vi rui.key &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;Let’s Encrypt的证书不能使用,需要注意的是先备份原证书,而且要备份到外部设备,如果只是改个名字重启后就会被删除.腾讯云的免费证书可以用&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;重启服务&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;services.sh restart &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;关闭SSH&lt;/h3&gt; &lt;p&gt;记得关闭SSH!&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/02/10/image-20250210102117087.png" alt="image-20250210102117087" title="image-20250210102117087" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 10 Feb 2025 02:22:00 GMT</pubDate>
    </item>
    <item>
      <title>10 Essential Terminal Commands Every Developer Should Know</title>
      <link>https://maruifu.cn/article/336</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;List of useful Unix terminal commands to boost your productivity. Here are some of my favorites.&lt;/p&gt; &lt;p&gt;Sometimes, tasks that might take hours to code can be accomplished in minutes with the terminal.&lt;/p&gt; &lt;p&gt;This article assumes you’re already comfortable with basic commands like &lt;strong&gt;rm&lt;/strong&gt;, &lt;strong&gt;pwd&lt;/strong&gt;, and &lt;strong&gt;cd&lt;/strong&gt;.&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;&lt;strong&gt;grep&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;Need to find where a function or variable is used in your codebase, or sift through logs to locate specific entries? grep can help you with that.&lt;/p&gt; &lt;p&gt;The &lt;strong&gt;grep&lt;/strong&gt; command searches for specific patterns in files. It’s like having a supercharged search function that digs into file contents.&lt;/p&gt; &lt;p&gt;The basic syntax for the grep command goes as the following;&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep &amp;quot;let's find something&amp;quot; file.[txt,json,js,md,etc] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Case-insensitive search:&lt;/strong&gt; Add the &lt;em&gt;-i&lt;/em&gt; flag to ignore case differences.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep -i &amp;quot;REact&amp;quot; compiler/apps/playground/app/index.tsx ? '[DEV] React Compiler Playground' : 'React Compiler Playground' &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Count occurrences:&lt;/strong&gt; Use the &lt;em&gt;-c&lt;/em&gt; flag to count the number of matching lines.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep -c &amp;quot;React&amp;quot; compiler/apps/playground/app/index.tsx &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Analyzing logs:&lt;/strong&gt; If you’re troubleshooting an issue, you can use grep to find specific error messages in logs.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep -i &amp;quot;Operation not supported on socket&amp;quot; system.log 09/24 08:51:01 INFO   :..settcpimage: Get TCP images rc - EDC8112I Operation not supported on socket. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Search for Multiple Patterns:&lt;/strong&gt; You can search for multiple patterns by using the &lt;strong&gt;-e&lt;/strong&gt; flag multiple times.&lt;/p&gt; &lt;p&gt;Match either &lt;em&gt;“error”&lt;/em&gt; or &lt;em&gt;“404”&lt;/em&gt; in &lt;em&gt;system.log&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep -e &amp;quot;error&amp;quot; -e &amp;quot;404&amp;quot; system.log npm error code E404 npm error 404  'trevorlasn.com@*' is not in this registry. npm error A complete log of this run can be found in: /Users/trevorindreklasn/.npm/_logs/2024-08-20T16_41_32_846Z-debug-0.log &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Recursive Search:&lt;/strong&gt; To search for a pattern in all files within a directory and its subdirectories, use the &lt;em&gt;-r&lt;/em&gt; (or —recursive) flag.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ grep -o -r &amp;quot;fs&amp;quot; node_modules | wc -l 22491 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;This will search through all files in the specified directory and its subdirectories. The &lt;strong&gt;-o&lt;/strong&gt; option tells grep to print only the matched parts of the line.&lt;/p&gt; &lt;p&gt;The pipe &lt;strong&gt;|&lt;/strong&gt; takes the output from the command on the left (grep) and uses it as input for the command on the right (&lt;em&gt;wc -l&lt;/em&gt;). &lt;strong&gt;wc -l&lt;/strong&gt; counts and displays the number of lines in its input.&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;man&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;The man command stands for “manual.” It helps you find detailed information about other commands and programs.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ man grep  NAME      grep, egrep, fgrep, rgrep, bzgrep, bzegrep, bzfgrep, zgrep,      zegrep, zfgrep – file pattern searcher  SYNOPSIS      grep [-abcdDEFGHhIiJLlMmnOopqRSsUVvwXxZz] [-A num] [-B num]           [-C num] [-e pattern] [-f file] [--binary-files=value]           [--color[=when]] [--colour[=when]] [--context=num]           [--label] [--line-buffered] [--null] [pattern] [file ...]  DESCRIPTION      The grep utility searches any given input files, selecting      lines that match one or more patterns.  By default, a pattern      matches an input line if the regular expression (RE) in the      pattern matches the input line without its trailing newline.      An empty expression matches every line.  Each input line that      matches at least one of the patterns is written to the standard      output.       grep is used for simple patterns and basic regular expressions      (BREs); egrep can handle extended regular expressions (EREs).      See re_format(7) for more information on regular expressions.      fgrep is quicker than both grep and egrep, but can only handle      fixed patterns (i.e., it does not interpret regular      expressions).  Patterns may consist of one or more lines,      allowing any of the pattern lines to match a portion of the      input.       zgrep, zegrep, and zfgrep act like grep, egrep, and fgrep,      respectively, but accept input files compressed with the      compress(1) or gzip(1) compression utilities.  bzgrep, bzegrep,      and bzfgrep act like grep, egrep, and fgrep, respectively, but      accept input files compressed with the bzip2(1) compression      utility.       The following options are available:       -A num, --after-context=num              Print num lines of trailing context after each match.              See also the -B and -C options.              ... &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;cat&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;The cat command is short for “concatenate.” It’s used to display the contents of a file, combine files, or create new ones.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ cat astro.config.mjs import { defineConfig } from &amp;quot;astro/config&amp;quot;; import mdx from &amp;quot;@astrojs/mdx&amp;quot;; import sitemap from &amp;quot;@astrojs/sitemap&amp;quot;; import tailwind from &amp;quot;@astrojs/tailwind&amp;quot;; import vercel from &amp;quot;@astrojs/vercel/static&amp;quot;; import partytown from &amp;quot;@astrojs/partytown&amp;quot;;  // https://astro.build/config export default defineConfig({   site: &amp;quot;https://www.trevorlasn.com&amp;quot;,   integrations: [ mdx(), sitemap(), tailwind(), partytown({     config: {       forward: [&amp;quot;dataLayer.push&amp;quot;]     }   }), ],   output: &amp;quot;static&amp;quot;,   adapter: vercel(), }); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Combining Files:&lt;/strong&gt; One of the key features of cat is its ability to combine multiple files into one. For instance, if you want to merge file1.txt and file2.txt into file3.txt, you can do this:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat file1.txt file2.txt &amp;gt; file3.txt &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;This command above takes the content of &lt;em&gt;file1.txt&lt;/em&gt; and &lt;em&gt;file2.txt&lt;/em&gt; and merges them into &lt;em&gt;file3.txt&lt;/em&gt;. The &lt;strong&gt;&amp;gt;&lt;/strong&gt; operator is used to direct the combined output into a new file.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Creating New Files:&lt;/strong&gt; You can also use &lt;em&gt;cat&lt;/em&gt; to create new files. Type your text, and when you’re done, press &lt;em&gt;Ctrl+D&lt;/em&gt; to save and exit.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat &amp;gt; newfile.txt hey ➜ ls newfile.txt ➜ cat newfile.txt hey &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;cat&lt;/strong&gt; is helpful for viewing smaller files, but for very large files, it can be overwhelming as it dumps everything at once. In such cases, it’s better to use commands like &lt;strong&gt;less&lt;/strong&gt; or &lt;strong&gt;head&lt;/strong&gt; to view files in a more controlled way.&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;head&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;You often don’t need to see all the content when working with large files. Instead of using cat to display everything, the head command lets you preview just the first few lines of a file.&lt;/p&gt; &lt;p&gt;This is especially useful for checking the structure of CSV files, logs, or any other large text files.&lt;/p&gt; &lt;p&gt;By default, &lt;strong&gt;head&lt;/strong&gt; shows the first 10 lines of a file:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ head package-lock.json {   &amp;quot;name&amp;quot;: &amp;quot;trevorlasn.com&amp;quot;,   &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,   &amp;quot;lockfileVersion&amp;quot;: 3,   &amp;quot;requires&amp;quot;: true,   &amp;quot;packages&amp;quot;: {     &amp;quot;&amp;quot;: {       &amp;quot;name&amp;quot;: &amp;quot;trevorlasn.com&amp;quot;,       &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,       &amp;quot;dependencies&amp;quot;: { &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;If you need more or fewer lines, you can specify the exact number using the &lt;strong&gt;-n&lt;/strong&gt; option:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ head -n 5 package-lock.json {   &amp;quot;name&amp;quot;: &amp;quot;trevorlasn.com&amp;quot;,   &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,   &amp;quot;lockfileVersion&amp;quot;: 3,   &amp;quot;requires&amp;quot;: true, &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Previewing CSV Headers:&lt;/strong&gt; For CSV files, head is perfect for quickly checking the header or structure:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ head -n 1 username-password-recovery-code.csv Username; Identifier;One-time password;Recovery code;First name;Last name;Department;Location &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;awk&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;awk&lt;/strong&gt; is a powerful tool for pattern scanning and processing. It’s particularly useful for manipulating and analyzing text files and data streams.&lt;/p&gt; &lt;p&gt;With awk, you can filter, extract, and transform data in a file or from command output.&lt;/p&gt; &lt;p&gt;awk efficiently extracts and combines data from various sources using its associative arrays. Suppose you have two CSV files:&lt;/p&gt; &lt;p&gt;List of employees.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat employees.csv ID,Name,Department 101,John Doe,Sales 102,Jane Smith,Engineering 103,Jim Brown,Sales &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;List of salaries.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat salaries.csv ID,Salary 101,50000 102,60000 103,55000 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Use &lt;strong&gt;awk&lt;/strong&gt; to merge these files and display each employee’s name with their salary.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ awk -F',' '     NR==FNR {salaries[$1]=$2; next}     FNR==1 {next}     {print $2, salaries[$1]} ' salaries.csv employees.csv John Doe 50000 Jane Smith 60000 Jim Brown 55000 &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;NR==FNR {salaries[$1]=$2; next}&lt;/strong&gt;: While processing the first file (&lt;strong&gt;salaries.csv&lt;/strong&gt;), store salaries in an associative array. The employee ID (&lt;strong&gt;$1&lt;/strong&gt;) is the key, and the salary (&lt;strong&gt;$2&lt;/strong&gt;) is the value. This runs only for the first file.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;FNR==1 {next}&lt;/strong&gt;: Skip the header line of the second file (&lt;strong&gt;employees.csv&lt;/strong&gt;).&lt;/li&gt; &lt;li&gt;&lt;strong&gt;{print $2, salaries[$1]}&lt;/strong&gt;: For each line in the second file (&lt;strong&gt;employees.csv&lt;/strong&gt;), print the employee’s name (&lt;strong&gt;$2&lt;/strong&gt;) and their salary from the array (&lt;strong&gt;salaries[$1]&lt;/strong&gt;).&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You can also save the results to a new file.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ awk -F',' '     NR==FNR {salaries[$1]=$2; next}     FNR==1 {next}     {print $2, salaries[$1]} ' salaries.csv employees.csv &amp;gt; combined.csv  ➜ cat combined.csv John Doe 50000 Jane Smith 60000 Jim Brown 55000 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;sed&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;sed&lt;/strong&gt;, short for Stream Editor, is a powerful tool for text processing in the terminal. It allows you to find, replace, insert, or delete text within files or streams of data.&lt;/p&gt; &lt;p&gt;You can use it for quick edits without opening a text editor, making it great for scripting and automation.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Replace a word or pattern in a file:&lt;/strong&gt; Replacing “Trevor” with “John”.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat hello.md My name is Trevor ➜ sed -i '' 's/Trevor/John/' hello.md ➜ cat hello.md My name is John &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;If you want save the changes, use the &lt;strong&gt;-i&lt;/strong&gt; option.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Print Specific Lines:&lt;/strong&gt; Print only specific lines from a file.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ sed -n '2,4p' package-lock.json   &amp;quot;name&amp;quot;: &amp;quot;trevorlasn.com&amp;quot;,   &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,   &amp;quot;lockfileVersion&amp;quot;: 3,  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;This prints lines 2 through 4.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Regular Expressions:&lt;/strong&gt; &lt;em&gt;sed&lt;/em&gt; supports regular expressions, allowing for complex search-and-replace operations. For example, replace all digits with “&lt;em&gt;X&lt;/em&gt;”:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat combined.csv John Doe 50000 Jane Smith 60000 Jim Brown 55000 ➜ sed 's/[0-9]/X/g' combined.csv John Doe XXXXX Jane Smith XXXXX Jim Brown XXXXX &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Renaming Files in Bulk:&lt;/strong&gt; Let’s say you have multiple files with the extension &lt;em&gt;.txt&lt;/em&gt; and you want to rename them to &lt;em&gt;.md&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ ls 1.txt 2.txt 3.txt ➜ for file in *.txt; do     mv &amp;quot;$file&amp;quot; &amp;quot;$(echo &amp;quot;$file&amp;quot; | sed 's/.txt$/.md/')&amp;quot;   done ➜ ls 1.md 2.md 3.md &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;em&gt;sed&lt;/em&gt; is highly versatile, and these examples just scratch the surface&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;tail&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;tail&lt;/strong&gt; is the counterpart to &lt;strong&gt;head&lt;/strong&gt;. It allows you to view the last few lines of a file rather than the first. It’s commonly used to monitor log files or check the end of a document. By default, tail shows the last 10 lines of a file.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ tail package.json   },   &amp;quot;devDependencies&amp;quot;: {     &amp;quot;@typescript-eslint/eslint-plugin&amp;quot;: &amp;quot;^7.3.1&amp;quot;,     &amp;quot;@typescript-eslint/parser&amp;quot;: &amp;quot;^7.3.1&amp;quot;,     &amp;quot;eslint&amp;quot;: &amp;quot;^8.57.0&amp;quot;,     &amp;quot;eslint-plugin-astro&amp;quot;: &amp;quot;^0.32.0&amp;quot;,     &amp;quot;eslint-plugin-jsx-a11y&amp;quot;: &amp;quot;^6.8.0&amp;quot;,     &amp;quot;typescript&amp;quot;: &amp;quot;^5.4.2&amp;quot;   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Viewing More or Fewer Lines:&lt;/strong&gt; You can adjust the number of lines shown using the &lt;em&gt;-n&lt;/em&gt; option.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  trevorlasn.com git:(master) ✗ tail -n 15 package.json     &amp;quot;astro&amp;quot;: &amp;quot;^4.13.3&amp;quot;,     &amp;quot;clsx&amp;quot;: &amp;quot;^2.1.0&amp;quot;,     &amp;quot;sharp&amp;quot;: &amp;quot;^0.33.3&amp;quot;,     &amp;quot;tailwind-merge&amp;quot;: &amp;quot;^2.2.2&amp;quot;,     &amp;quot;tailwindcss&amp;quot;: &amp;quot;^3.4.1&amp;quot;   },   &amp;quot;devDependencies&amp;quot;: {     &amp;quot;@typescript-eslint/eslint-plugin&amp;quot;: &amp;quot;^7.3.1&amp;quot;,     &amp;quot;@typescript-eslint/parser&amp;quot;: &amp;quot;^7.3.1&amp;quot;,     &amp;quot;eslint&amp;quot;: &amp;quot;^8.57.0&amp;quot;,     &amp;quot;eslint-plugin-astro&amp;quot;: &amp;quot;^0.32.0&amp;quot;,     &amp;quot;eslint-plugin-jsx-a11y&amp;quot;: &amp;quot;^6.8.0&amp;quot;,     &amp;quot;typescript&amp;quot;: &amp;quot;^5.4.2&amp;quot;   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Real-Time File Monitoring:&lt;/strong&gt; One of the most powerful features of tail is the &lt;em&gt;-f&lt;/em&gt; option, which allows you to follow a file as it grows. This is especially useful for watching log files in real-time.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ tail -f 1.md 11 Changing file &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;As new lines are added to &lt;strong&gt;1.md&lt;/strong&gt;, tail will automatically display them.&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;chmod&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;Each file has three sets of permissions: &lt;strong&gt;owner&lt;/strong&gt;, &lt;strong&gt;group&lt;/strong&gt;, and &lt;strong&gt;others&lt;/strong&gt;. These are typically represented in a format like &lt;strong&gt;rwxr-xr—&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;r:&lt;/strong&gt; Read permission&lt;/li&gt; &lt;li&gt;&lt;strong&gt;w:&lt;/strong&gt; Write permission&lt;/li&gt; &lt;li&gt;&lt;strong&gt;x:&lt;/strong&gt; Execute permission&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ ls -l sensitive.md -rw-r--r--@ 1 trevorindreklasn  staff  0 Aug 21 15:22 sensitive.md &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The file permissions -rw-r—r— indicate that:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Owner (trevorindreklasn):&lt;/strong&gt; Has read (r) and write (w) permissions.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Group (staff):&lt;/strong&gt; Has read (r) permissions.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Others:&lt;/strong&gt; Have read (r) permissions.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The &lt;strong&gt;@&lt;/strong&gt; symbol indicates that the file has extended attributes, which are additional metadata beyond standard file permissions.&lt;/p&gt; &lt;p&gt;File permissions control who can read, write, or execute a file, ensuring security and proper access management by preventing unauthorized users from modifying or viewing sensitive data.&lt;/p&gt; &lt;p&gt;To restrict access to &lt;em&gt;&lt;strong&gt;sensitive.md&lt;/strong&gt;&lt;/em&gt; so that only the root user or superadmins can view and write to it, you can use the chmod command to modify the file’s permissions.&lt;/p&gt; &lt;p&gt;First, ensure the file is owned by the root user or a superadmin. You might need sudo for changing ownership:&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ ls -l sensitive.md -rw-r--r--@ 1 root  staff  0 Aug 21 15:22 sensitive.md  ➜ sudo chown root:admin sensitive.md ➜ ls -l sensitive.md -rw-r-----@ 1 root  admin  0 Aug 21 15:22 sensitive.md  ➜ sudo chmod 600 sensitive.md ➜  textfiles ls -l sensitive.md -rw-------  1 root  admin  0 Aug 21 15:22 sensitive.md &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Only the owner (root) has read and write access. While the group and others have no permissions. This restricts access to the file, making it readable and writable only by the owner.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Improper file permissions can lead to security issues or system problems&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Unauthorized Access:&lt;/strong&gt; File containing sensitive information, like passwords or financial data, has overly permissive settings (e.g., &lt;em&gt;chmod 777&lt;/em&gt;), anyone on the system can read or modify it. This could lead to data breaches or unauthorized access to sensitive information.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Malware Installation:&lt;/strong&gt; A file or directory with write permissions for all users (e.g., &lt;em&gt;chmod 777&lt;/em&gt;) could be exploited by attackers to place malicious scripts or software, potentially compromising the entire system.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Data Corruption:&lt;/strong&gt; If files that should be read-only (e.g., logs or system configurations) are accidentally given write permissions, users or applications might inadvertently corrupt or erase critical data, leading to system instability or loss of important information.&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;strong&gt;xargs&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;The &lt;strong&gt;xargs&lt;/strong&gt; command builds and runs commands using input from other commands. It’s used to pass a list of items as arguments to another command.&lt;/p&gt; &lt;p&gt;Suppose you have a list of files that you want to delete.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ ls 1.txt        2.txt       2.md         3.md         sensitive.md  # Find all .txt files and delete them ➜ find . -name &amp;quot;*.txt&amp;quot; | xargs rm  ➜  ls 2.md         3.md         sensitive.md &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Instead of deleting them one by one, you can use xargs to pass the list of files to &lt;em&gt;rm&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;find . -name &amp;quot;*.tmp&amp;quot; | xargs rm &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Creating Multiple Directories:&lt;/strong&gt; If you have a list of directory names in a file and want to create all of them, you can use xargs with mkdir.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ cat dirs.txt src temp utils public  ➜ ls 2.md         3.md         dirs.txt     sensitive.md  # Creates each directory listed in the file. ➜ cat dirs.txt | xargs mkdir  ➜ ls 2.md         dirs.txt     sensitive.md temp 3.md         public       src          utils &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Compressing Files:&lt;/strong&gt; If you have multiple files that you want to compress using gzip, you can use xargs to pass the filenames to gzip.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# Compresses all .log files in the current directory ls *.log | xargs gzip &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;find&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;Search and locate files and directories within your file system based on various criteria. It’s highly customizable and can be combined with other commands for complex tasks.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;find [path] [expression] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The find command searches for occurrences of “astro” within the &lt;em&gt;node_modules&lt;/em&gt; directory, returning paths to files and directories with that name, including executables and package files.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ trevorlasn.com git:(master) ✗ find node_modules -name &amp;quot;astro&amp;quot;  node_modules/.bin/astro  node_modules/astro  node_modules/astro/dist/runtime/server/render/astro &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Cleanup old log files:&lt;/strong&gt; Regularly delete log files older than a month to free up disk space.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜ find /var/log -type f -name &amp;quot;*.log&amp;quot; -mtime +30 -delete &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Backup important files:&lt;/strong&gt; Locate and copy all &lt;strong&gt;.docx&lt;/strong&gt; files from home directory to a backup location.&lt;/p&gt; &lt;p&gt;Terminal window&lt;/p&gt; &lt;pre&gt;&lt;code&gt;➜  find ~/Documents -name &amp;quot;*.docx&amp;quot; -exec cp {} /path/to/backup/ \; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;strong&gt;find&lt;/strong&gt; command is incredibly versatile and can be tailored to suit a wide range of file management tasks.&lt;/p&gt; &lt;p&gt;转自:https://www.trevorlasn.com/blog/10-essential-terminal-commands-every-developer-should-know&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Jan 2025 08:12:00 GMT</pubDate>
    </item>
    <item>
      <title>MAC本地微调大模型（MLX + Qwen2.5）</title>
      <link>https://maruifu.cn/article/334</link>
      <content:encoded>&lt;h2&gt;背景&lt;/h2&gt; &lt;p&gt;微调过程中的各种数据集清洗、微调超参数调整学习，最新模型的测试，都需要一个高效的微调框架去进行验证、熟悉，本文就通过介绍苹果官方出品的MLX微调框架本地微调大模型。毕竟想随心所欲的使用云端微调服务，大部分都是需要收费，在没有对微调技术较为熟悉的情况下，本地微调大模型也是不失为一种ROI较高的学习方式。&lt;/p&gt; &lt;h2&gt;微调&lt;/h2&gt; &lt;p&gt;微调是基于一个已经训练好的神经网络模型，通过对其参数进行细微调整，使其更好地适应特定的任务或数据。通过在新的小规模数据集上继续训练模型的部分或全部层，模型能够在保留原有知识的基础上，针对新任务进行优化，从而提升在特定领域的表现。 根据微调参数范围划分，微调范围分为两种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;全微调（Full Fine-tuning）&lt;/strong&gt;：就是对整个预训练模型来个全套改造，包括所有的模型参数。这种招式适合任务和预训练模型之间相差大的情况，或者任务要求模型超级灵活自适应的时候。虽然这招消耗资源多，时间也长，但效果杠杠的。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;部分微调（Partial Fine-Tuning）&lt;/strong&gt;：这个招式就是只调整模型的上层或者少数几层，底层参数不动。这招适合任务和预训练模型比较相似，或者数据集不大的情况。因为只动少数层，所以资源消耗少，速度快，不过有时候效果可能差点。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;目前我们绝大部分场景都使用的是部分微调，种方法减少了计算和存储成本，同时降低了过拟合的风险，适合数据较少的任务，但在任务复杂度较高时可能无法充分发挥模型的潜力。&lt;/p&gt; &lt;p&gt;根据微调使用的数据集类型，大模型微调还可以分为：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;监督微调（Supervised Fine-tuning）&lt;/strong&gt;：就是用有标签的训练数据集进行微调。这些标签告诉模型在微调中应该怎么做。比如分类任务，每个样本都有对应的标签。用这些标签指导模型微调，可以让它更适应具体任务。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;无监督微调（Unsupervised Fine-tuning）&lt;/strong&gt;：这个就是用无标签的训练数据集进行微调。也就是模型只能看数据，不知道啥是对啥是错。这种方法通过学习数据内在结构或者生成数据，来提取有用特征或者改善模型表示能力。 本文后续微调采用的是SFT的方式进行微调，微调的理论我就不做过多篇幅介绍，有兴趣的同学可以自行搜索资料，本文重点是希望通过实操的方式，让大家对大模型的微调有亲身操作的体感，从而激发大家对于模型微调的研究兴趣。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;好处&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;提高准确性：微调可以显着提高大模型在特定任务上的准确性。&lt;/li&gt; &lt;li&gt;提高效率：可以使大模型在特定任务.上更有效。例如，在对问答数据集进行微调后，大模型可能能够更快、更准确地回答问题。&lt;/li&gt; &lt;li&gt;提高泛化能力：可以提高大模型的泛化能力，这意味着它们可以更好地执行与训练数据中数据不同的任务。例如，在对不同类型的创意文本格式(如诗歌、代码、脚本、音乐作品、电子邮件、信件等)的数据集进行微调后，大模型可能能够生成我们用看件下微调大模型的优可以减少训练大模型所需的数据量。&lt;/li&gt; &lt;li&gt;数据安全：如果数据敏感，内部组织可能更愿意使用微调而不是公开模型，以保护数据隐私。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;流程&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;准备数据集：找到和任务相关的数据，保证数据质量和标签准确，然后做好清洗和预处理。&lt;/li&gt; &lt;li&gt;选模型：根据任务和数据，选一个合适的微调的基座模型。&lt;/li&gt; &lt;li&gt;微调策略参数：根据任务需求和资源，选择合适的微调策略，配置LoRA参数、微调参数如学习率，确保模型收敛。&lt;/li&gt; &lt;li&gt;训练模型：在训练集上训练模型，，按照设定的超参数和优化算法，调整参数降低损失，防止过拟合。&lt;/li&gt; &lt;li&gt;评估模型：在验证集上评估模型性能。&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;MLX&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/01/06/image-20250106092225428.png" alt="image-20250106092225428" title="image-20250106092225428" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;MLX是由苹果的机器学习研究团队推出的用于机器学习的阵列框架，该开源框架专为 Apple Silicon 芯片而设计优化，从NumPy、PyTorch、Jax和ArrayFire等框架中吸取灵感，提供简单友好的使用方法，它可以在Apple Silicon CPU/GPU 上进行 ML 训练和推理。由Apple公司开发的 MLX 库类似于 TensorFlow 和 PyTorch，支持 GPU 支持的任务。该库允许在新的 Apple Silicon（M 系列）芯片上对 LLM 进行微调。此外，MLX 还支持使用 LoRA 、QLoRA等方法对 LLM 进行微调 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;官网：&lt;a href="https://ml-explore.github.io/mlx/build/html/index.html" target="_blank"&gt;ml-explore.github.io/mlx/build/h…&lt;/a&gt; github：&lt;a href="https://github.com/ml-explore/mlx" target="_blank"&gt;github.com/ml-explore/…&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;下载模型&lt;/h2&gt; &lt;p&gt;通过命令行下载模型，模型大小1G左右，下载命令如下，整体过程可以跑满下载带宽。&lt;/p&gt; &lt;h3&gt;创建环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;conda create -n FineTune python=3.11.11 conda activate FineTune &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;下载模型&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#安装依赖 可能需要科学 pip install -U huggingface_hub #设置环境变量 export HF_ENDPOINT=https://hf-mirror.com  #下载模型，保存至qwen2.5-0.5B目录 huggingface-cli download --resume-download Qwen/Qwen2.5-0.5B-Instruct --local-dir qwen2.5-0.5B &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;下载失败可以重复执行一下&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;下载完成后的文件列表&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/01/06/image-20250102102235910.png" alt="image-20250102102235910" title="image-20250102102235910" /&gt;&lt;/p&gt; &lt;h2&gt;准备数据集&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;{&amp;quot;prompt&amp;quot;: &amp;quot;今天星期几&amp;quot;, &amp;quot;completion&amp;quot;: &amp;quot;星期八&amp;quot;} {&amp;quot;prompt&amp;quot;: &amp;quot;太阳什么时候升起?&amp;quot;, &amp;quot;completion&amp;quot;: &amp;quot;晚上八点&amp;quot;} {&amp;quot;prompt&amp;quot;: &amp;quot;忘情水是什么水&amp;quot;, &amp;quot;completion&amp;quot;: &amp;quot;忘情水是可以让人忘却烦恼的水&amp;quot;} {&amp;quot;prompt&amp;quot;: &amp;quot;蓝牙耳机坏了应该看什么科&amp;quot;, &amp;quot;completion&amp;quot;: &amp;quot;耳鼻喉科&amp;quot;} {&amp;quot;prompt&amp;quot;: &amp;quot;鲁迅为什么讨厌周树人&amp;quot;, &amp;quot;completion&amp;quot;: &amp;quot;因为他们是仇人&amp;quot;}  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;代码准备&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git clone git@github.com:ml-explore/mlx-examples.git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;将lora/data目录下的“train.jsonl”文件内容改为上面的微调数据集，由于我们不做测试与验证，所以测试数据集和验证数据集的内容就不修改了。验证方式通过对微调后的模型进行提问来简单验证。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/01/06/image-20250102103416564.png" alt="image-20250102103416564" title="image-20250102103416564" /&gt;&lt;/p&gt; &lt;h2&gt;依赖安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pip install mlx-lm pip install transformers pip install torch pip install numpy &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;微调模型&lt;/h2&gt; &lt;p&gt;进入lora目录，执行如下代码开始微调，由于我们目的是快速走通微调流程，就不去调整各种微调超参去测试微调效果了，仅指定需要微调的模型路径与数据集路径，其他参数走默认参数。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd /Users/maruifu/Desktop/AI/mlx-examples/lora mlx_lm.lora --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --train --data ./data &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;支持的微调方式包括lora、qlora、full（全参微调）&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;开始执行微调过程，由于数据集数量很少，执行过程很快，loss也下降的很快&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(FineTune) maruifu@XMG-M4ProMax lora % mlx_lm.lora --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --train --data ./data Loading pretrained model Loading datasets Training Trainable parameters: 0.109% (0.541M/494.033M) Starting training..., iters: 1000 Iter 1: Val loss 2.755, Val took 2.665s Iter 10: Train loss 5.209, Learning Rate 1.000e-05, It/sec 6.475, Tokens/sec 1120.136, Trained Tokens 1730, Peak mem 2.007 GB Iter 20: Train loss 2.642, Learning Rate 1.000e-05, It/sec 9.758, Tokens/sec 1688.204, Trained Tokens 3460, Peak mem 2.007 GB Iter 30: Train loss 1.472, Learning Rate 1.000e-05, It/sec 9.751, Tokens/sec 1686.856, Trained Tokens 5190, Peak mem 2.007 GB Iter 40: Train loss 0.911, Learning Rate 1.000e-05, It/sec 9.773, Tokens/sec 1690.745, Trained Tokens 6920, Peak mem 2.007 GB Iter 50: Train loss 0.615, Learning Rate 1.000e-05, It/sec 9.508, Tokens/sec 1644.925, Trained Tokens 8650, Peak mem 2.007 GB Iter 60: Train loss 0.413, Learning Rate 1.000e-05, It/sec 9.701, Tokens/sec 1678.330, Trained Tokens 10380, Peak mem 2.007 GB Iter 70: Train loss 0.248, Learning Rate 1.000e-05, It/sec 9.745, Tokens/sec 1685.828, Trained Tokens 12110, Peak mem 2.007 GB Iter 80: Train loss 0.132, Learning Rate 1.000e-05, It/sec 9.744, Tokens/sec 1685.675, Trained Tokens 13840, Peak mem 2.007 GB Iter 90: Train loss 0.087, Learning Rate 1.000e-05, It/sec 9.737, Tokens/sec 1684.579, Trained Tokens 15570, Peak mem 2.007 GB Iter 100: Train loss 0.067, Learning Rate 1.000e-05, It/sec 9.653, Tokens/sec 1669.912, Trained Tokens 17300, Peak mem 2.007 GB Iter 100: Saved adapter weights to adapters/adapters.safetensors and adapters/0000100_adapters.safetensors. Iter 110: Train loss 0.060, Learning Rate 1.000e-05, It/sec 9.692, Tokens/sec 1676.734, Trained Tokens 19030, Peak mem 2.007 GB Iter 120: Train loss 0.055, Learning Rate 1.000e-05, It/sec 9.355, Tokens/sec 1618.465, Trained Tokens 20760, Peak mem 2.007 GB Iter 130: Train loss 0.053, Learning Rate 1.000e-05, It/sec 9.727, Tokens/sec 1682.790, Trained Tokens 22490, Peak mem 2.007 GB Iter 140: Train loss 0.049, Learning Rate 1.000e-05, It/sec 9.707, Tokens/sec 1679.372, Trained Tokens 24220, Peak mem 2.007 GB Iter 150: Train loss 0.048, Learning Rate 1.000e-05, It/sec 9.714, Tokens/sec 1680.461, Trained Tokens 25950, Peak mem 2.007 GB Iter 160: Train loss 0.046, Learning Rate 1.000e-05, It/sec 9.678, Tokens/sec 1674.324, Trained Tokens 27680, Peak mem 2.007 GB Iter 170: Train loss 0.046, Learning Rate 1.000e-05, It/sec 9.542, Tokens/sec 1650.749, Trained Tokens 29410, Peak mem 2.007 GB Iter 180: Train loss 0.045, Learning Rate 1.000e-05, It/sec 9.681, Tokens/sec 1674.849, Trained Tokens 31140, Peak mem 2.007 GB Iter 190: Train loss 0.044, Learning Rate 1.000e-05, It/sec 9.620, Tokens/sec 1664.245, Trained Tokens 32870, Peak mem 2.007 GB Iter 200: Val loss 2.911, Val took 1.904s Iter 200: Train loss 0.044, Learning Rate 1.000e-05, It/sec 79.695, Tokens/sec 13787.287, Trained Tokens 34600, Peak mem 2.021 GB Iter 200: Saved adapter weights to adapters/adapters.safetensors and adapters/0000200_adapters.safetensors. Iter 210: Train loss 0.042, Learning Rate 1.000e-05, It/sec 9.653, Tokens/sec 1669.991, Trained Tokens 36330, Peak mem 2.021 GB Iter 220: Train loss 0.042, Learning Rate 1.000e-05, It/sec 9.545, Tokens/sec 1651.331, Trained Tokens 38060, Peak mem 2.021 GB Iter 230: Train loss 0.041, Learning Rate 1.000e-05, It/sec 9.635, Tokens/sec 1666.780, Trained Tokens 39790, Peak mem 2.021 GB Iter 240: Train loss 0.042, Learning Rate 1.000e-05, It/sec 9.524, Tokens/sec 1647.641, Trained Tokens 41520, Peak mem 2.021 GB Iter 250: Train loss 0.041, Learning Rate 1.000e-05, It/sec 9.610, Tokens/sec 1662.556, Trained Tokens 43250, Peak mem 2.021 GB Iter 260: Train loss 0.041, Learning Rate 1.000e-05, It/sec 9.489, Tokens/sec 1641.622, Trained Tokens 44980, Peak mem 2.021 GB Iter 270: Train loss 0.039, Learning Rate 1.000e-05, It/sec 9.682, Tokens/sec 1675.023, Trained Tokens 46710, Peak mem 2.021 GB Iter 280: Train loss 0.039, Learning Rate 1.000e-05, It/sec 9.622, Tokens/sec 1664.556, Trained Tokens 48440, Peak mem 2.021 GB Iter 290: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.673, Tokens/sec 1673.366, Trained Tokens 50170, Peak mem 2.021 GB Iter 300: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.561, Tokens/sec 1654.011, Trained Tokens 51900, Peak mem 2.021 GB Iter 300: Saved adapter weights to adapters/adapters.safetensors and adapters/0000300_adapters.safetensors. Iter 310: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.613, Tokens/sec 1663.060, Trained Tokens 53630, Peak mem 2.021 GB Iter 320: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.650, Tokens/sec 1669.448, Trained Tokens 55360, Peak mem 2.021 GB Iter 330: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.650, Tokens/sec 1669.512, Trained Tokens 57090, Peak mem 2.021 GB Iter 340: Train loss 0.039, Learning Rate 1.000e-05, It/sec 9.703, Tokens/sec 1678.638, Trained Tokens 58820, Peak mem 2.021 GB Iter 350: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.567, Tokens/sec 1655.136, Trained Tokens 60550, Peak mem 2.021 GB Iter 360: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.584, Tokens/sec 1657.958, Trained Tokens 62280, Peak mem 2.021 GB Iter 370: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.583, Tokens/sec 1657.870, Trained Tokens 64010, Peak mem 2.021 GB Iter 380: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.584, Tokens/sec 1657.979, Trained Tokens 65740, Peak mem 2.021 GB Iter 390: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.658, Tokens/sec 1670.766, Trained Tokens 67470, Peak mem 2.021 GB Iter 400: Val loss 2.923, Val took 1.876s Iter 400: Train loss 0.037, Learning Rate 1.000e-05, It/sec 70.241, Tokens/sec 12151.771, Trained Tokens 69200, Peak mem 2.021 GB Iter 400: Saved adapter weights to adapters/adapters.safetensors and adapters/0000400_adapters.safetensors. Iter 410: Train loss 0.038, Learning Rate 1.000e-05, It/sec 9.660, Tokens/sec 1671.174, Trained Tokens 70930, Peak mem 2.021 GB Iter 420: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.672, Tokens/sec 1673.215, Trained Tokens 72660, Peak mem 2.021 GB Iter 430: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.672, Tokens/sec 1673.253, Trained Tokens 74390, Peak mem 2.021 GB Iter 440: Train loss 0.037, Learning Rate 1.000e-05, It/sec 9.677, Tokens/sec 1674.095, Trained Tokens 76120, Peak mem 2.021 GB Iter 450: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.677, Tokens/sec 1674.059, Trained Tokens 77850, Peak mem 2.021 GB Iter 460: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.640, Tokens/sec 1667.714, Trained Tokens 79580, Peak mem 2.021 GB Iter 470: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.569, Tokens/sec 1655.492, Trained Tokens 81310, Peak mem 2.021 GB Iter 480: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.662, Tokens/sec 1671.469, Trained Tokens 83040, Peak mem 2.021 GB Iter 490: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.599, Tokens/sec 1660.569, Trained Tokens 84770, Peak mem 2.021 GB Iter 500: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.292, Tokens/sec 1607.535, Trained Tokens 86500, Peak mem 2.021 GB Iter 500: Saved adapter weights to adapters/adapters.safetensors and adapters/0000500_adapters.safetensors. Iter 510: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.631, Tokens/sec 1666.207, Trained Tokens 88230, Peak mem 2.021 GB Iter 520: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.650, Tokens/sec 1669.500, Trained Tokens 89960, Peak mem 2.021 GB Iter 530: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.666, Tokens/sec 1672.258, Trained Tokens 91690, Peak mem 2.021 GB Iter 540: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.625, Tokens/sec 1665.041, Trained Tokens 93420, Peak mem 2.021 GB Iter 550: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.623, Tokens/sec 1664.755, Trained Tokens 95150, Peak mem 2.021 GB Iter 560: Train loss 0.036, Learning Rate 1.000e-05, It/sec 9.636, Tokens/sec 1667.108, Trained Tokens 96880, Peak mem 2.021 GB Iter 570: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.682, Tokens/sec 1674.924, Trained Tokens 98610, Peak mem 2.021 GB Iter 580: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.674, Tokens/sec 1673.663, Trained Tokens 100340, Peak mem 2.021 GB Iter 590: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.613, Tokens/sec 1663.114, Trained Tokens 102070, Peak mem 2.021 GB Iter 600: Val loss 2.914, Val took 1.905s Iter 600: Train loss 0.035, Learning Rate 1.000e-05, It/sec 78.821, Tokens/sec 13636.113, Trained Tokens 103800, Peak mem 2.021 GB Iter 600: Saved adapter weights to adapters/adapters.safetensors and adapters/0000600_adapters.safetensors. Iter 610: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.362, Tokens/sec 1619.649, Trained Tokens 105530, Peak mem 2.021 GB Iter 620: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.369, Tokens/sec 1620.838, Trained Tokens 107260, Peak mem 2.021 GB Iter 630: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.408, Tokens/sec 1627.645, Trained Tokens 108990, Peak mem 2.021 GB Iter 640: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.428, Tokens/sec 1631.094, Trained Tokens 110720, Peak mem 2.021 GB Iter 650: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.460, Tokens/sec 1636.570, Trained Tokens 112450, Peak mem 2.021 GB Iter 660: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.505, Tokens/sec 1644.345, Trained Tokens 114180, Peak mem 2.021 GB Iter 670: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.595, Tokens/sec 1659.934, Trained Tokens 115910, Peak mem 2.021 GB Iter 680: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.625, Tokens/sec 1665.183, Trained Tokens 117640, Peak mem 2.021 GB Iter 690: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.619, Tokens/sec 1664.156, Trained Tokens 119370, Peak mem 2.021 GB Iter 700: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.641, Tokens/sec 1667.847, Trained Tokens 121100, Peak mem 2.021 GB Iter 700: Saved adapter weights to adapters/adapters.safetensors and adapters/0000700_adapters.safetensors. Iter 710: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.637, Tokens/sec 1667.250, Trained Tokens 122830, Peak mem 2.021 GB Iter 720: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.659, Tokens/sec 1671.092, Trained Tokens 124560, Peak mem 2.021 GB Iter 730: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.651, Tokens/sec 1669.550, Trained Tokens 126290, Peak mem 2.021 GB Iter 740: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.636, Tokens/sec 1667.022, Trained Tokens 128020, Peak mem 2.021 GB Iter 750: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.651, Tokens/sec 1669.658, Trained Tokens 129750, Peak mem 2.021 GB Iter 760: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.624, Tokens/sec 1664.878, Trained Tokens 131480, Peak mem 2.021 GB Iter 770: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.694, Tokens/sec 1677.147, Trained Tokens 133210, Peak mem 2.021 GB Iter 780: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.659, Tokens/sec 1670.965, Trained Tokens 134940, Peak mem 2.021 GB Iter 790: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.652, Tokens/sec 1669.851, Trained Tokens 136670, Peak mem 2.021 GB Iter 800: Val loss 2.931, Val took 1.918s Iter 800: Train loss 0.035, Learning Rate 1.000e-05, It/sec 70.855, Tokens/sec 12257.872, Trained Tokens 138400, Peak mem 2.021 GB Iter 800: Saved adapter weights to adapters/adapters.safetensors and adapters/0000800_adapters.safetensors. Iter 810: Train loss 0.034, Learning Rate 1.000e-05, It/sec 8.452, Tokens/sec 1462.112, Trained Tokens 140130, Peak mem 2.021 GB Iter 820: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.346, Tokens/sec 1616.807, Trained Tokens 141860, Peak mem 2.021 GB Iter 830: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.096, Tokens/sec 1573.530, Trained Tokens 143590, Peak mem 2.021 GB Iter 840: Train loss 0.034, Learning Rate 1.000e-05, It/sec 8.860, Tokens/sec 1532.860, Trained Tokens 145320, Peak mem 2.021 GB Iter 850: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.409, Tokens/sec 1627.715, Trained Tokens 147050, Peak mem 2.021 GB Iter 860: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.511, Tokens/sec 1645.410, Trained Tokens 148780, Peak mem 2.021 GB Iter 870: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.524, Tokens/sec 1647.594, Trained Tokens 150510, Peak mem 2.021 GB Iter 880: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.534, Tokens/sec 1649.359, Trained Tokens 152240, Peak mem 2.021 GB Iter 890: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.167, Tokens/sec 1585.897, Trained Tokens 153970, Peak mem 2.021 GB Iter 900: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.380, Tokens/sec 1622.811, Trained Tokens 155700, Peak mem 2.021 GB Iter 900: Saved adapter weights to adapters/adapters.safetensors and adapters/0000900_adapters.safetensors. Iter 910: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.480, Tokens/sec 1640.073, Trained Tokens 157430, Peak mem 2.021 GB Iter 920: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.489, Tokens/sec 1641.646, Trained Tokens 159160, Peak mem 2.021 GB Iter 930: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.523, Tokens/sec 1647.469, Trained Tokens 160890, Peak mem 2.021 GB Iter 940: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.282, Tokens/sec 1605.724, Trained Tokens 162620, Peak mem 2.021 GB Iter 950: Train loss 0.034, Learning Rate 1.000e-05, It/sec 8.924, Tokens/sec 1543.773, Trained Tokens 164350, Peak mem 2.021 GB Iter 960: Train loss 0.034, Learning Rate 1.000e-05, It/sec 9.216, Tokens/sec 1594.282, Trained Tokens 166080, Peak mem 2.021 GB Iter 970: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.420, Tokens/sec 1629.594, Trained Tokens 167810, Peak mem 2.021 GB Iter 980: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.440, Tokens/sec 1633.078, Trained Tokens 169540, Peak mem 2.021 GB Iter 990: Train loss 0.035, Learning Rate 1.000e-05, It/sec 9.433, Tokens/sec 1631.846, Trained Tokens 171270, Peak mem 2.021 GB Iter 1000: Val loss 2.941, Val took 2.211s Iter 1000: Train loss 0.035, Learning Rate 1.000e-05, It/sec 66.983, Tokens/sec 11588.048, Trained Tokens 173000, Peak mem 2.021 GB Iter 1000: Saved adapter weights to adapters/adapters.safetensors and adapters/0001000_adapters.safetensors. Saved final weights to adapters/adapters.safetensors. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在训练1000Iter后，最终在生成了lora目录下生成微调后的模型适配器权重文件目录adapters&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/01/16/image-20250102105443671.png" alt="image-20250102105443671" title="image-20250102105443671" /&gt;&lt;/p&gt; &lt;h3&gt;合并模型&lt;/h3&gt; &lt;p&gt;将原始模型通过mlx_lm.fuse命令生成与低秩适配器融合后新的模型，新模型命名为“qwen2.5-0.5B-new” 融合成功后生成新模型文件夹“qwen2.5-0.5B-new”&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mlx_lm.fuse --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --adapter-path adapters --save-path qwen2.5-0.5B-new &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/01/06/image-20250102105734935.png" alt="image-20250102105734935" title="image-20250102105734935" /&gt;&lt;/p&gt; &lt;h3&gt;验证效果&lt;/h3&gt; &lt;p&gt;本次验证由于不用于实际项目，所以就通过测试数据集来验证效果，如果有需要，可以通过自定义“test.jsonl”数据集运行下面命令，计算perplexity&lt;/p&gt; &lt;pre&gt;&lt;code&gt;python lora.py --model &amp;lt;path_to_model&amp;gt; \                --adapter-file &amp;lt;path_to_adapters.npz&amp;gt; \                --test &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;本次验证通过简单推理几个问题来验证微调后的模型效果，推理命令示例如下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#原始模型推理问题 mlx_lm.generate --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --prompt &amp;quot;蓝牙耳机坏了应该看什么科&amp;quot; #微调后的模型推理问题 mlx_lm.generate --model qwen2.5-0.5B-new --prompt &amp;quot;蓝牙耳机坏了应该看什么科&amp;quot;   #原始模型推理问题 mlx_lm.generate --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --prompt &amp;quot;今天星期几?&amp;quot; #微调后的模型推理问题 mlx_lm.generate --model qwen2.5-0.5B-new --prompt &amp;quot;今天星期几?&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;(FineTune) maruifu@XMG-M4ProMax lora % mlx_lm.generate --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --prompt &amp;quot;蓝牙耳机坏了应该看什么科&amp;quot; ========== Prompt: &amp;lt;|im_start|&amp;gt;system You are Qwen, created by Alibaba Cloud. You are a helpful assistant.&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;user 蓝牙耳机坏了应该看什么科&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;assistant  蓝牙耳机坏了，通常需要检查以下几个方面来确定问题所在：  1. **电源和连接线**：确保耳机的电源线和连接线没有损坏。如果连接线松动或损坏，可能会导致耳机无法正常工作。  2. **耳机内部**：检查耳机内部是否有损坏或松动的部件。例如，如果耳机的麦克风或扬声器出现问题，可能会导致耳机无法正常工作。  3. **耳机的电池**：如果耳机的电池已经 ========== Prompt: 36 tokens, 639.492 tokens-per-sec Generation: 100 tokens, 220.372 tokens-per-sec Peak memory: 1.005 GB (FineTune) maruifu@XMG-M4ProMax lora % mlx_lm.generate --model qwen2.5-0.5B-new --prompt &amp;quot;蓝牙耳机坏了应该看什么科&amp;quot; ========== Prompt: &amp;lt;|im_start|&amp;gt;system You are Qwen, created by Alibaba Cloud. You are a helpful assistant.&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;user 蓝牙耳机坏了应该看什么科&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;assistant  耳鼻喉科 ========== Prompt: 36 tokens, 607.020 tokens-per-sec Generation: 5 tokens, 244.010 tokens-per-sec Peak memory: 1.005 GB (FineTune) maruifu@XMG-M4ProMax lora % mlx_lm.generate --model /Users/maruifu/Desktop/AI/qwen2.5-0.5B --prompt &amp;quot;今天星期几?&amp;quot;              ========== Prompt: &amp;lt;|im_start|&amp;gt;system You are Qwen, created by Alibaba Cloud. You are a helpful assistant.&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;user 今天星期几?&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;assistant  很抱歉，我无法直接获取当前日期和时间。不过，我可以帮助你查询或回答关于日期和时间的问题。请告诉我你需要查询的具体日期和时间，我会尽力提供帮助。 ========== Prompt: 33 tokens, 501.995 tokens-per-sec Generation: 41 tokens, 221.940 tokens-per-sec Peak memory: 1.004 GB (FineTune) maruifu@XMG-M4ProMax lora % mlx_lm.generate --model qwen2.5-0.5B-new --prompt &amp;quot;今天星期几?&amp;quot;                                    ========== Prompt: &amp;lt;|im_start|&amp;gt;system You are Qwen, created by Alibaba Cloud. You are a helpful assistant.&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;user 今天星期几?&amp;lt;|im_end|&amp;gt; &amp;lt;|im_start|&amp;gt;assistant  星期八 ========== Prompt: 33 tokens, 600.904 tokens-per-sec Generation: 3 tokens, 279.420 tokens-per-sec Peak memory: 1.004 GB (FineTune) maruifu@XMG-M4ProMax lora %   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看到，微调后的模型已经起作用了&lt;/p&gt; &lt;h2&gt;写到最后&lt;/h2&gt; &lt;p&gt;本次介绍苹果官方出品的MLX微调框架简单微调过程，希望能帮助对微调感兴趣的同学，理解微调过程的各种环节。虽然本地微调框架很难用于实际的生产项目，但对微调流程中的“数据清洗”、&amp;quot;超参调整&amp;quot;、&amp;quot;模型验证&amp;quot;等微调环节的学习，还是能起到积极正向的效果，使得大家对模型微调越来越熟悉、越来越有感觉。&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 06 Jan 2025 01:23:00 GMT</pubDate>
    </item>
    <item>
      <title>信创中间件如何选型</title>
      <link>https://maruifu.cn/article/333</link>
      <content:encoded>&lt;p&gt;随着各行各业对IT系统自主可控的需求不断增强，企业对信创中间件的应用越来越广泛和深入。在以往信创试点过程中，中间件是有名录的，而截止目前国测发布的《&lt;a href="http://www.itsec.gov.cn/aqkkcp/cpgg/" target="_blank"&gt;安全可靠测评结果&lt;/a&gt;》也未包含信创中间件，面对种类繁多的信创中间件产品，企业需要从中间件第三方评测、实际测试选型多个维度进行评估，以选择最适合自己的产品，为避免品牌倾向，本文不涉及具体品牌讨论，围绕信创中间件的定义、分类以及中间件的选型测试实践经验和注意事项而展开。&lt;/p&gt; &lt;h2&gt;定义与分类&lt;/h2&gt; &lt;p&gt;为解决异构问题，提出了中间件概念，中间件是位于平台（硬件和操作系统）和应用之间的通用服务，这些服务具有标准的程序接口和协议。&lt;/p&gt; &lt;p&gt;针对不同的操作系统和硬件平台，有符合接口和协议规范的多种实现。可以为处于上层的应用软件提供运行与开发环境，封装不同应用系统的API接口，为应用提供统一标准接口，使应用的开发、运行与操作系统解耦，屏蔽底层的技术细节差异，确保应用的独立性。&lt;/p&gt; &lt;p&gt;中间件位于操作系统之上，应用软件之下，处于信息系统的中间位置，故而得名。中间件并不是一种软件，而是一类软件统称；中间件不仅仅实现互连，还要实现应用之间的互操作。它犹如一道屏障，有效屏蔽了底层操作系统所具有的复杂性，从而为上层应用软件在开发、运行以及集成等诸多环节提供了极大的便利。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210134952750.png" alt="image-20241210134952750" title="image-20241210134952750" /&gt;&lt;/p&gt; &lt;p&gt;中间件可以分为基础类中间件、数据类中间件和云计算类中间件。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210135003974.png" alt="image-20241210135003974" title="image-20241210135003974" /&gt;&lt;/p&gt; &lt;h2&gt;产品选型测试&lt;/h2&gt; &lt;p&gt;在信创项目上马之前，为保障项目顺利推进，当前由于面向企业市场的信创中间件品牌众多，质量参差不齐等方面原因，一般都会开展POC(验证性测试)，也是业界流行的针对客户具体应用的验证性测试，而在POC阶段应该如何选择哪些品牌，缩小POC范围呢？笔者建议从以下进行考量。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、市场占有率：&lt;/strong&gt; 虽然市场占有率高的中间件不一定是最佳选择，但它至少表明了产品经过市场验证并得到了用户广泛认可。这些产品往往问题较多，迭代更新也更为迅速。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2、技术栈兼容性：&lt;/strong&gt; 市场上的中间件技术栈各异，建议在POC阶段测试涵盖多种技术栈类型的中间件，以确保兼容性和多样性，如消息中间件、Web中间件、分布式中间件、事务性中间件等需要按照不同类型中间件确保兼容性。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3、功能性需求：&lt;/strong&gt; 鉴于信创中间件功能相对与非信创中间件差异性，如常用的中间件涉及java容器、web负载、缓存、消息队列、数据同步等，&lt;strong&gt;建议根据非信创中间件形成功能性需求清单&lt;/strong&gt;，并在测试阶段对信创中间件进行逐项评估。评估指标包括&lt;strong&gt;缓存策略、并发吞吐性能、与开发框架的兼容性、数据同步校验方式以及消息中间件的消息丢失问题&lt;/strong&gt;等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4、性能需求：&lt;/strong&gt; 建议首先&lt;strong&gt;确定一个性能基准&lt;/strong&gt;，即现有中间件的性能数据。然后，使用这些数据作为参考，对不同品牌的信创中间件进行相同的性能测试，以便进行比较分析。例如，&lt;strong&gt;可以先测试Redis等现有中间件的缓存读取性能，再对比测试其他信创中间件的相应性能&lt;/strong&gt;，需要注意的是，由于客观条件的限制，信创中间件在某些性能指标上可能不如现有的中间件，这是正常现象。因此，在性能测试中，应更加关注那些对企业业务至关重要的性能指标，而不是所有指标。具体来说：&lt;/p&gt; &lt;p&gt;(1)如果企业主要关注缓存中间件的读取性能，并且不涉及持久化操   作，那么在测试中可以不过分关注持久化性能。&lt;/p&gt; &lt;p&gt;(2) 如果企业使用Java容器主要处理短连接场景，那么对于长连接性能  的测试可以不那么重视。&lt;/p&gt; &lt;p&gt;通过这种方式，企业可以更准确地评估信创中间件的性能，确保它们满足业务需求，同时优化资源投入和测试重点。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5、非功能性需求：&lt;/strong&gt; 信创中间件在满足非功能性需求方面可能存在不足，建议制定非功能性需求清单，包括&lt;strong&gt;安全性、自动化安装、日志管理、平台集成能力和监控功能，以及资源占用情况&lt;/strong&gt;等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6、稳定性测试需求：&lt;/strong&gt; 对于信创中间件的稳定性测试，&lt;strong&gt;建议实施至少一个月的实践测试周期&lt;/strong&gt;。在这个月内，应对包括Java容器、负载均衡、缓存和消息队列在内的各类信创中间件进行详细的测试，并记录它们的运行状况。根据经验，以下是在这一测试期间可能遇到的两种主要场景：&lt;/p&gt; &lt;p&gt;（1）内存溢出问题：可能由于应用程序代码编写不当导致的内存溢出，这与信创中间件的性能无关；另一种可能是信创中间件本身存在问题导致的内存溢出。如果厂商未能提供修复版本，那么这类中间件可能不适合使用。&lt;/p&gt; &lt;p&gt;（2）性能随时间下降：随着运行时间的延长，中间件的响应速度可能会逐渐变慢。这种情况需要在测试中观察并处理，因为它可能影响长期的系统稳定性和性能。&lt;strong&gt;通过这样的长期稳定性测试，可以更准确地评估信创中间件在实际应用中的可靠性和性能表现，从而为企业选择适合的中间件产品提供重要依据&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;7、高可靠需求：&lt;/strong&gt; 信创中间件的高可靠性测试可分为两大类架构形态：分布式架构和传统双节点架构。以下是对这两类架构的高可用性测试的总结：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;分布式架构中间件：利用Paxos和Raft等分布式协议实现高可用性，确保系统在故障或网络分区情况下数据一致性。 测试场景包括停止单个服务进程、停止所有进程、重复启动服务进程、停止master节点和停止证书所在节点。特别关注那些容易被忽略的场景，如停止所有进程、重复启动服务进程和停止证书所在节点，因为这些场景对于验证分布式中间件的高可用性至关重要。&lt;/li&gt; &lt;li&gt;双节点架构中间件：包括主备和双活两种类型，通常基于集群间内存复制实现高可用性。测试时，通过关闭一个节点来验证负载是否能自动切换到另一个节点，以及业务受影响的时间。由于内存复制可能导致性能下降，一般建议在双节点前部署负载均衡器，以实现应用服务的高可用性，而不是依赖于集群间内存复制。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;8、厂商响应速度：&lt;/strong&gt; 在沟通过程中，厂商对问题的响应速度可以反映其对客户的重视程度。一些厂商能够及时反馈，而其他厂商可能响应较慢，这可以作为评估厂商服务水平的一个指标。&lt;/p&gt; &lt;h2&gt;结语&lt;/h2&gt; &lt;p&gt;由于目前信创中间件的评估，国测并没有发布，市场上信创中间件的评估很多是通过第三方评测单位开展，可选范围较多，笔者建议先少量采购，并且在一些非核心系统上使用，总结各种使用经验，以便在出现问题时能够及时得到支持。&lt;/p&gt; &lt;p&gt;以上总结poc测试的8个方面的考量，总体来说，信创中间件相比于非信创中间件虽然存在差异，但是整体在可接受的范围内，需要大家一起努力共建良好的信创中间件生态。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;本文转自 &lt;a href="javascript:void(0);" target="_blank"&gt;信创新态势&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Tue, 10 Dec 2024 05:53:00 GMT</pubDate>
    </item>
    <item>
      <title>搭建本地大模型之LM Studio</title>
      <link>https://maruifu.cn/article/332</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;之前分享了 Ollama 这次分享一下 另一款本地运行大模型的工具LM Studio&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;什么是 LM Studio？&lt;/h2&gt; &lt;p&gt;LM Studio 是一款用于在您的电脑上开发和实验LLMs的桌面应用程序。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;关键功能&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;桌面应用程序，用于运行本地 LLMs&lt;/li&gt; &lt;li&gt;一个熟悉的聊天界面&lt;/li&gt; &lt;li&gt;搜索和下载功能（通过 Hugging Face 🤗）&lt;/li&gt; &lt;li&gt;一个可以监听类似 OpenAI 端点的本地服务器&lt;/li&gt; &lt;li&gt;本地模型和配置管理系统&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;系统要求&lt;/h2&gt; &lt;p&gt;LM Studio 通常支持 Apple Silicon Macs、x64/ARM64 Windows PC 和 x64 Linux PC。&lt;/p&gt; &lt;h3&gt;macOS&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;芯片：苹果硅（M1/M2/M3/M4）。&lt;/li&gt; &lt;li&gt;macOS 13.4 或更高版本是必需的。 &lt;ul&gt; &lt;li&gt;对于 MLX 模型，需要 macOS 14.0 或更高版本。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;16GB+内存推荐。&lt;/li&gt; &lt;li&gt;您可能仍然可以在 8GB 的 Mac 上使用 LM Studio，但请坚持使用较小型号和适度的上下文大小。&lt;/li&gt; &lt;li&gt;英特尔 Mac 目前不支持。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Windows&lt;/h3&gt; &lt;p&gt;LM Studio 支持 x64 和 ARM（Snapdragon X Elite）架构的系统。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;CPU：需要支持 AVX2 指令集（针对 x64）&lt;/li&gt; &lt;li&gt;RAM: LLMs可能会消耗大量 RAM。建议至少 16GB 的 RAM。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Linux&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;LM Studio for Linux 以 AppImage 的形式分发。&lt;/li&gt; &lt;li&gt;Ubuntu 20.04 或更高版本是必需的&lt;/li&gt; &lt;li&gt;仅支持 x64，暂不支持 aarch64&lt;/li&gt; &lt;li&gt;Ubuntu 版本高于 22 的版本测试不佳。&lt;/li&gt; &lt;li&gt;CPU：中央处理器&lt;/li&gt; &lt;li&gt;LM Studio 默认支持 AVX2&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;如何安装 LM Studio？&lt;/h2&gt; &lt;p&gt;LM Studio 适用于 macOS、Windows 和 Linux。&lt;/p&gt; &lt;p&gt;前往&lt;a href="https://lmstudio.ai/download" target="_blank"&gt;下载页面&lt;/a&gt;并下载适用于您的操作系统的安装程序。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210100843826.png" alt="image-20241210100843826" title="image-20241210100843826" /&gt;&lt;/p&gt; &lt;h2&gt;设置中文&lt;/h2&gt; &lt;p&gt;右下角设置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210101036618.png" alt="image-20241210101036618" title="image-20241210101036618" /&gt;&lt;/p&gt; &lt;h2&gt;设置模型目录&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;模型目录最好不要有中文特殊符号,最好是英文字母&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210101220794.png" alt="image-20241210101220794" title="image-20241210101220794" /&gt;&lt;/p&gt; &lt;h2&gt;下载模型&lt;/h2&gt; &lt;h3&gt;如果可以科学上网&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210101553375.png" alt="image-20241210101553375" title="image-20241210101553375" /&gt;&lt;/p&gt; &lt;h3&gt;镜像网站下载&lt;/h3&gt; &lt;p&gt;https://hf-mirror.com/&lt;/p&gt; &lt;p&gt;下载gguf模型放到前面设置的模型目录里面&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210103319008.png" alt="image-20241210103319008" title="image-20241210103319008" /&gt;&lt;/p&gt; &lt;h3&gt;模型挑选&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/10/image-20241210103439099.png" alt="image-20241210103439099" title="image-20241210103439099" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;模型文件大小小于自己显存大小,但接近显存大小的效果肯定越好&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;GGUF模型&lt;/h3&gt; &lt;p&gt;GGUF，全称 GPT-Generated Unified Format，是一种新型的文件格式专门用于存储和交换大型机器学习模型的数据。这种格式针对模型的快速加载和保存进行了优化，使其在推理方面更加高效。GGUF可以有多重不同版本的量化，Q2、Q3、Q4、Q5、Q6、Q8，这些数字表示模型权重的位数，位数越高，模型的精度通常越高，但所需的存储空间和计算资源也越多。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;不是所有 gguf 格式的模都能用 LM Studio 运行，你可以打开下面这个链接，这里所有模型都可以用LM Studio 运行:https://hf-mirror.com/lmstudio-community *量化是指将模型中的高精度数字转换为低精度数字，以减少模型的存储空间和计算需求。&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Tue, 10 Dec 2024 02:44:07 GMT</pubDate>
    </item>
    <item>
      <title>Docker容器内无法解析域名：IP  Temporary failure in name resolution</title>
      <link>https://maruifu.cn/article/331</link>
      <content:encoded>&lt;h2&gt;检查主机网络设置&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;cat /proc/sys/net/ipv4/ip_forward &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;返回&lt;code&gt;0&lt;/code&gt;则说明未打开，开启ip地址转发：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-cobol"&gt;vi /etc/sysctl.conf &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;打开内核配置文件，查找&lt;code&gt;net.ipv4.ip_forward = 0&lt;/code&gt;，将其修改为&lt;code&gt;net.ipv4.ip_forward = 1&lt;/code&gt;，若该行开头有&lt;code&gt;#&lt;/code&gt;，将其去掉；若配置文件中没有此项，则在文件最后添加此内容。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 11   12 # Controls IP packet forwarding  13 net.ipv4.ip_forward = 1  14  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;保存修改后，重启系统或输入以下命令使修改生效：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sysctl -p /etc/sysctl.conf systemctl restart network &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;检查主机防火墙配置&lt;/h2&gt; &lt;p&gt;查看防火墙状态（若防火墙为关闭状态，可跳过防火墙有关设置）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo firewall-cmd --state &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;若返回runging，则防火墙为开启状态，查看防火墙是否开启ip地址转发（ip地址伪装）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo firewall-cmd --query-masquerade &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;若返回no，则输入以下命令开启ip地址转发&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo firewall-cmd --add-masquerade --permanent &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后输入以下命令使修改生效：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo firewall-cmd --reload &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再次检查问题是否已经解决，若没有则进行再一步排查&lt;/p&gt; &lt;h2&gt;设置Docker指定DNS服务器&lt;/h2&gt; &lt;p&gt;打开Docker相关设置文件（主机内）&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;没有就新建一个，输入下列命令会打开或自动新建：&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;vi /etc/docker/daemon.json &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在文件中输入以下内容&lt;/p&gt; &lt;pre&gt;&lt;code&gt;{  &amp;quot;dns&amp;quot;: [&amp;quot;8.8.8.8&amp;quot;,&amp;quot;114.114.114.114&amp;quot;] } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;重启Docker&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl restart docker &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 06 Dec 2024 06:28:01 GMT</pubDate>
    </item>
    <item>
      <title>Linux下安装Git</title>
      <link>https://maruifu.cn/article/330</link>
      <content:encoded>&lt;h1&gt;1. 安装Git&lt;/h1&gt; &lt;h2&gt;1.1 Ubuntu12.04下&lt;/h2&gt; &lt;p&gt;可以使用apt-get方式安装，也可以下载源代码安装【1】，我们这里使用apt-git安装。&lt;/p&gt; &lt;p&gt;但由于直接使用 &lt;code&gt;sudo apt-get install git&lt;/code&gt;安装的版本较老，因此我们参考【2】中给出的PPA源。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo add-apt-repository ppa:git-core/ppa sudo apt-get update sudo apt-get install git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装完成后，检查是否安装成功&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git --version &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;显示 git version 2.1.1，表明安装成功。&lt;/p&gt; &lt;h2&gt;1.2 CentOS6.6下&lt;/h2&gt; &lt;p&gt;在CentOS5的时代，由于yum源中没有git，所以需要预先安装一系列的依赖包。但在CentOS6的yum源中已经有git的版本了，可以直接使用yum源进行安装。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ sudo yum install git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是yum源中安装的git版本是1.7.1，太老了，Github等需要的Git版本最低都不能低于1.7.2 。所以我们一般不用上面的方法。而是下载git源码，编译安装。&lt;/p&gt; &lt;p&gt;编译安装的步骤是【4】：&lt;/p&gt; &lt;p&gt;（1）首先先更新系统&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo yum update &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（2）安装依赖的包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（3）下载git源码并解压缩&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ wget https://github.com/git/git/archive/v2.21.0.zip $ unzip v2.21.0.zip $ cd git-2.21.0 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（4）编译安装&lt;/p&gt; &lt;p&gt;将其安装在“/usr/local/git”目录下。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;make make prefix=/usr/local/git install &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（5）此时你如果使用git --version 查看git版本的话，发现git仍然是1.7.1版本。这是因为它默认使用了&amp;quot;/usr/bin&amp;quot;下的git。&lt;/p&gt; &lt;p&gt;你可以用下面的命令查看git所在的路径：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ whereis git git: /usr/bin/git /usr/local/git /usr/share/man/man1/git.1.gz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（6）我们要把编译安装的git路径放到环境变量里，让它替换&amp;quot;/usr/bin&amp;quot;下的git。为此我们可以修改“/etc/profile”文件（或者/etc/bashrc文件）。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo vim /etc/profile &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后在文件的最后一行，添加下面的内容，然后保存退出。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;export PATH=/usr/local/git/bin:$PATH &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（7）使用source命令应用修改。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;source /etc/profile &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（8）然后再次使用git --version 查看git版本，发现输出2.21.0，表明安装成功。&lt;/p&gt; &lt;h1&gt;2. 设置Git&lt;/h1&gt; &lt;p&gt;（1）设置用户名和email。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git config --global user.name &amp;quot;Your Name&amp;quot; git config --global user.email &amp;quot;youremail@domain.com&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时，Home目录下会新建一个.gitconfig文件&lt;/p&gt; &lt;h1&gt;3. 为GitHub账号添加SSH Keys&lt;/h1&gt; &lt;p&gt;以公钥认证方式访问SSH协议的Git服务器时无需输入口令，而且更安全。（访问HTTP协议的Git服务器时，比如提交修改，每次都需要输入口令。）&lt;/p&gt; &lt;p&gt;（1）创建SSH key&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ ssh-keygen -t rsa -C &amp;quot;email@maruifu.cn&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;系统会提示key的保存位置（一般是~/.ssh目录）和指定口令，保持默认，连续三次回车即可。&lt;/p&gt; &lt;p&gt;（2）Copy SSH Key&lt;/p&gt; &lt;p&gt;然后用vim打开该文件，id_rsa.pub文件内的内容，粘帖到github帐号管理的添加SSH key界面中。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim ~/.ssh/id_rsa.pub &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（3）添加到GitHub&lt;/p&gt; &lt;p&gt;登录github-&amp;gt; Accounting settings图标-&amp;gt; SSH key-&amp;gt; Add SSH key-&amp;gt; 填写SSH key的名称（可以起一个自己容易区分的），然后将拷贝的~/.ssh/id_rsa.pub文件内容粘帖-&amp;gt; add key”按钮添加。&lt;/p&gt; &lt;p&gt;（4）测试&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ssh -T git@github.com &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;4. 为GitHub上的Repository提交修改&lt;/h1&gt; &lt;p&gt;（1）git clone已存在GitHub上的Repository。（在新建的~/MyTestFolder目录中）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git clone https://github.com/MaRuifu/maruifu.git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（2）修改一个文件，然后提交&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim README.md git status git add README.md git status git commit -m &amp;quot;init&amp;quot; git status git remote add origin https://github.com/MaRuifu/maruifu.git &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这时会报错误：&lt;/p&gt; &lt;p&gt;fatal: remote origin already exists.&lt;/p&gt; &lt;p&gt;解决办法【3】：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ git remote rm origin &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后再次执行 git remote add origin https://github.com/MaRuifu/maruifu.git&lt;/p&gt; &lt;p&gt;（3）之后，需要将修改push到GitHub上&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git push -u origin master &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行该条命令后，会要求输入GitHub账户的用户名和密码。&lt;/p&gt; &lt;p&gt;（4）提交完成后，查看GitHub上的Repository，会发现内容修改成功。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 03 Dec 2024 08:26:12 GMT</pubDate>
    </item>
    <item>
      <title>浏览器的数据存储方法比较</title>
      <link>https://maruifu.cn/article/329</link>
      <content:encoded>&lt;h2&gt;现代浏览器中可用的存储 API&lt;/h2&gt; &lt;p&gt;首先，让我们简要概述一下不同的 API、它们的目的用例和历史：&lt;/p&gt; &lt;h3&gt;什么是 Cookies&lt;/h3&gt; &lt;p&gt;Cookies 首次由&lt;a href="https://www.baekdal.com/thoughts/the-original-cookie-specification-from-1997-was-gdpr-compliant/" target="_blank"&gt;netscape&lt;/a&gt;在 1994 年引入。Cookies 存储一些键值数据的小片段，主要用于会话管理、个性化跟踪。Cookies 可以设置多个安全选项，如生存时间或&lt;code&gt;域名&lt;/code&gt;属性，以便在多个子域之间共享 Cookies。&lt;/p&gt; &lt;p&gt;Cookies 的值不仅存储在客户端，还与每个 HTTP 请求一起发送到服务器。这意味着我们无法在 Cookie 中存储大量数据，但与其他方法相比，Cookie 的访问性能仍然很有趣。特别是由于 Cookie 是网络的一个重要基本功能，已经进行了许多性能优化，甚至在这些日子里，像 chromium 的&lt;a href="https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html" target="_blank"&gt;共享内存版本&lt;/a&gt;或异步的&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API" target="_blank"&gt;CookieStore API&lt;/a&gt;这样的进步仍在继续。&lt;/p&gt; &lt;h3&gt;什么是 LocalStorage&lt;/h3&gt; &lt;p&gt;localStorage API 首次于 2009 年作为[WebStorage 规范的一部分被提出。LocalStorage 提供了一个简单的 API，用于在网页浏览器内部存储键值对。它具有&lt;code&gt;setItem&lt;/code&gt;、&lt;code&gt;getItem&lt;/code&gt;、&lt;code&gt;removeItem&lt;/code&gt;和&lt;code&gt;clear&lt;/code&gt;等方法，这些都是从键值存储中所需的所有功能。LocalStorage 仅适用于存储需要跨会话持久的小量数据，并且它受到5MB 存储容量的限制。存储复杂数据只能通过将其转换为字符串来实现，例如使用&lt;code&gt;JSON.stringify()&lt;/code&gt;。该 API 不是异步的，这意味着在进行操作时将完全阻塞您的 JavaScript 进程。因此，在它上面运行重操作可能会阻止 UI 渲染。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;也存在 &lt;strong&gt;SessionStorage&lt;/strong&gt; API。主要区别在于 localStorage 数据会无限期持续，直到明确清除，而 sessionStorage 数据在浏览器标签页或窗口关闭时会被清除。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;什么是 IndexedDB&lt;/h3&gt; &lt;p&gt;IndexedDB 首次作为“索引数据库 API”于 2015 年推出。&lt;a href="https://www.w3.org/TR/IndexedDB/#sotd" target="_blank"&gt;在&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;IndexedDB是一个用于存储大量结构化 JSON 数据的低级 API。虽然该 API 使用起来有些困难，但 IndexedDB 可以利用索引和异步操作。它不支持复杂查询，并且只允许遍历索引，这使得它更像是一个其他库的基础层，而不是一个完整的数据库。&lt;/p&gt; &lt;p&gt;2018 年，引入了 IndexedDB 版本 2.0 &lt;a href="https://hacks.mozilla.org/2016/10/whats-new-in-indexeddb-2-0/" target="_blank"&gt;。这增加了一些主要改进。最显著的是&lt;code&gt;getAll()&lt;/code&gt;方法，在获取大量 JSON 文档时显著提高了性能。&lt;/a&gt;&lt;/p&gt; &lt;p&gt;IndexedDB &lt;a href="https://w3c.github.io/IndexedDB/" target="_blank"&gt;版本 3.0&lt;/a&gt;正在开发中，其中包含许多改进。最重要的是增加了基于&lt;code&gt;Promise&lt;/code&gt;的调用，这使得现代 JS 特性如&lt;code&gt;async/await&lt;/code&gt;更加有用。&lt;/p&gt; &lt;h3&gt;什么是 OPFS&lt;/h3&gt; &lt;p&gt;《&lt;a href="https://rxdb.info/rx-storage-opfs.html" target="_blank"&gt;原始私有文件系统&lt;/a&gt;》（OPFS）是一个&lt;a href="https://caniuse.com/mdn-api_filesystemfilehandle_createsyncaccesshandle" target="_blank"&gt;相对较新的&lt;/a&gt;API，允许 Web 应用程序直接在浏览器中存储大文件。它旨在为想要在模拟文件系统中写入和读取&lt;strong&gt;二进制数据&lt;/strong&gt;的数据密集型应用程序设计。&lt;/p&gt; &lt;p&gt;OPFS 可以使用两种模式：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;或者异步在主线程上&lt;/li&gt; &lt;li&gt;或者在 WebWorker 中使用更快的异步访问，通过&lt;code&gt;createSyncAccessHandle()&lt;/code&gt;方法。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;因为只有二进制数据可以被处理，OPFS 被设计成库开发者的基础文件系统。当你构建一个“正常”的应用程序时，你不太可能直接在你的代码中使用 OPFS，因为它太复杂了。这仅适用于存储像图像这样的普通文件，而不是高效地存储和查询 JSON 数据。我为 RxDB 构建了一个基于 OPFS 的存储，并进行了适当的索引和查询，这花了我几个月的时间。&lt;/p&gt; &lt;h3&gt;什么是 WASM SQLite&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/12/03/info_files_icons_sqlite.svg.png" alt="info_files_icons_sqlite.svg" title="info_files_icons_sqlite.svg" /&gt; &lt;a href="https://webassembly.org/" target="_blank"&gt;WebAssembly&lt;/a&gt;（Wasm）是一种允许在网络上执行高性能代码的二进制格式。Wasm 于 2017 年被添加到主流浏览器中，这为在浏览器内部运行的内容开辟了广泛的机会。您可以将本地库编译成 WebAssembly，只需进行少量调整即可在客户端运行。WASM 代码可以发送到浏览器应用程序，通常比 JavaScript 运行得快得多，但仍然比本地代码慢约&lt;a href="https://www.usenix.org/conference/atc19/presentation/jangda" target="_blank"&gt;10%&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;许多人开始将编译后的 SQLite 用作浏览器内的数据库，这就是为什么将这种设置与原生 API 进行比较也很有意义。&lt;/p&gt; &lt;p&gt;SQLite 编译的字节码大小约为 938.9 KB，必须在第一次页面加载时由用户下载并解析。&lt;a href="https://sqlite.org/download.html" target="_blank"&gt;WASM 不能直接访问浏览器中的任何持久存储 API。&lt;/a&gt;相反，它需要数据从 WASM 流向主线程，然后才能放入浏览器 API 之一。这是通过所谓的&lt;a href="https://www.sqlite.org/vfs.html" target="_blank"&gt;虚拟文件系统（VFS）适配器&lt;/a&gt;来完成的，它处理从 SQLite 到其他任何数据访问。&lt;/p&gt; &lt;h3&gt;什么是 WebSQL&lt;/h3&gt; &lt;p&gt;WebSQL &lt;strong&gt;曾是&lt;/strong&gt;一个在 2009 年&lt;a href="https://www.w3.org/TR/webdatabase/" target="_blank"&gt;引入&lt;/a&gt;的 Web API，允许浏览器使用基于 SQLite 的 SQL 数据库进行客户端存储。该想法是为开发者提供一种在客户端使用 SQL 存储和查询数据的方法，类似于服务器端数据库。由于多个良好原因，WebSQL 在近年已被从浏览器中&lt;strong&gt;移除&lt;/strong&gt;。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;WebSQL 没有标准化，基于 SQLite 源代码这种单一具体实现的 API 很难使其成为标准。&lt;/li&gt; &lt;li&gt;WebSQL 需要浏览器使用 SQLite（版本 3.6.19）的特定版本（&lt;a href="https://developer.chrome.com/blog/deprecating-web-sql#reasons_for_deprecating_web_sql" target="_blank"&gt;特定版本&lt;/a&gt;），这意味着每当 SQLite 有任何更新或错误修复时，都无法将其添加到 WebSQL 中，否则可能会破坏网络。&lt;/li&gt; &lt;li&gt;主流浏览器如 Firefox 从未支持 WebSQL。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;因此，在以下内容中，我们甚至会忽略 WebSQL，即使通过设置特定的浏览器标志或使用旧版本的 Chromium 来运行测试也是可能的。&lt;/p&gt; &lt;h2&gt;功能比较&lt;/h2&gt; &lt;p&gt;现在您已经了解了 API 的基本概念，让我们比较一些对使用 RxDB 和基于浏览器的存储的人来说非常重要的特定功能。&lt;/p&gt; &lt;h3&gt;存储复杂的 JSON 文档&lt;/h3&gt; &lt;p&gt;当你在一个 Web 应用程序中存储数据时，通常你想要存储复杂的 JSON 文档，而不仅仅是存储在服务器端数据库中的“正常”值，如整数和字符串。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;仅 IndexedDB 原生支持 JSON 对象。&lt;/li&gt; &lt;li&gt;使用 SQLite WASM，您可以从版本 3.38.0（2022-02-22）开始，在&lt;a href="https://www.sqlite.org/json1.html" target="_blank"&gt;文本&lt;/a&gt;列中存储 JSON，甚至可以对它进行深度查询，并使用单个属性作为索引。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;每个其他 API 只能存储字符串或二进制数据。当然，您可以使用&lt;code&gt;JSON.stringify()&lt;/code&gt;将任何 JSON 对象转换为字符串，但如果没有在 API 中支持 JSON，则在运行查询时会使事情变得复杂。多次运行&lt;code&gt;JSON.stringify()&lt;/code&gt;可能会引起性能问题。&lt;/p&gt; &lt;h3&gt;多标签支持&lt;/h3&gt; &lt;p&gt;构建 Web 应用与Electron或React-Native相比的一个显著区别是，用户将同时在多个浏览器标签中打开和关闭应用。因此，您不仅有一个 JavaScript 进程在运行，而且可能有多个进程存在，并且可能需要在它们之间共享状态变化，以避免向用户显示&lt;strong&gt;过时数据。&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;如果你的用户在浏览你的网站时，肌肉记忆让左手放在了键上，那么你肯定做错了什么！&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;并非所有存储 API 都支持在标签页之间自动共享写事件的方式。&lt;/p&gt; &lt;p&gt;只有 localStorage 有通过 API 本身以自动在标签页之间共享写事件的方式，该事件可用于观察变化。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-js"&gt;// localStorage can observe changes with the storage event. // This feature is missing in IndexedDB and others addEventListener(&amp;quot;storage&amp;quot;, (event) =&amp;gt; {}); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;存在一个用于 Chrome 的&lt;a href="https://stackoverflow.com/a/33270440" target="_blank"&gt;实验性 IndexedDB 观察者 API&lt;/a&gt;，但该提案仓库已被存档。&lt;/p&gt; &lt;p&gt;为了解决这个问题，有两种解决方案：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;第一个选项是使用&lt;a href="https://github.com/pubkey/broadcast-channel" target="_blank"&gt;BroadcastChannel API&lt;/a&gt;，它可以在浏览器标签页之间发送消息。所以每次您向存储写入时，也会向其他标签页发送通知，告知它们这些更改。这是最常用的解决方案，RxDB 也使用了它。请注意，还有一个&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API" target="_blank"&gt;WebLocks API&lt;/a&gt;，可以用于在浏览器标签页之间实现互斥锁。&lt;/li&gt; &lt;li&gt;另一种解决方案是使用&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" target="_blank"&gt;SharedWorker&lt;/a&gt;并在其中执行所有写入操作。然后所有浏览器标签都可以订阅来自那个&lt;strong&gt;单个&lt;/strong&gt;SharedWorker 的消息，并了解变化。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;索引支持&lt;/h3&gt; &lt;p&gt;数据库与在普通文件中存储数据之间的主要区别在于，数据库以允许在索引上运行操作以简化快速查询的格式写入数据。在我们的技术列表中，只有 &lt;strong&gt;IndexedDB&lt;/strong&gt; 和 &lt;strong&gt;WASM SQLite&lt;/strong&gt; 支持开箱即用的索引功能。从理论上讲，您可以在任何存储上构建索引，如 localstorage 或 OPFS，但您可能不想自己这样做。&lt;/p&gt; &lt;p&gt;在 IndexedDB 中，例如，我们可以通过给定的索引范围获取大量文档：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-ts"&gt;// find all products with a price between 10 and 50 const keyRange = IDBKeyRange.bound(10, 50); const transaction = db.transaction('products', 'readonly'); const objectStore = transaction.objectStore('products'); const index = objectStore.index('priceIndex'); const request = index.getAll(keyRange); const result = await new Promise((res, rej) =&amp;gt; {   request.onsuccess = (event) =&amp;gt; res(event.target.result);   request.onerror = (event) =&amp;gt; rej(event); }); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;请注意，IndexedDB 有一个限制，即&lt;a href="https://github.com/w3c/IndexedDB/issues/76" target="_blank"&gt;布尔值没有索引&lt;/a&gt;。您只能索引字符串和数字。为了解决这个问题，您需要在存储数据时将布尔值转换为数字，并在读取时反向转换。&lt;/p&gt; &lt;h3&gt;WebWorker 支持&lt;/h3&gt; &lt;p&gt;在运行大量数据处理操作时，您可能希望将处理过程从 JavaScript 主线程移开。这确保了我们的应用始终保持响应和快速，同时处理可以在后台并行运行。在浏览器中，您可以使用&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" target="_blank"&gt;WebWorker&lt;/a&gt;、&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" target="_blank"&gt;SharedWorker&lt;/a&gt;或&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" target="_blank"&gt;ServiceWorker&lt;/a&gt; API 来完成此操作。在 RxDB 中，您可以使用WebWorker或SharedWorker插件将您的存储移动到工作线程内部。&lt;/p&gt; &lt;p&gt;该用例最常用的 API 是通过创建一个&lt;strong&gt;WebWorker&lt;/strong&gt;并在第二个 JavaScript 进程中完成大部分工作。这个工作进程是从一个单独的 JavaScript 文件（或 base64 字符串）中生成的，并通过使用&lt;code&gt;postMessage()&lt;/code&gt;发送数据与主线程进行通信。&lt;/p&gt; &lt;p&gt;很不幸，由于设计和安全限制，&lt;strong&gt;LocalStorage&lt;/strong&gt; 和 &lt;strong&gt;Cookies&lt;/strong&gt; 以及 &lt;a href="https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker" target="_blank"&gt;不能在 WebWorker 或 SharedWorker 中使用&lt;/a&gt;。WebWorkers 在与主浏览器线程分离的独立全局上下文中运行，因此无法执行可能影响主线程的操作。它们无法直接访问某些 Web API，如 DOM、localStorage 或 cookies。&lt;/p&gt; &lt;p&gt;所有其他内容都可以在 WebWorker 内部使用。带有&lt;code&gt;createSyncAccessHandle&lt;/code&gt;方法的 OPFS 快速版本可以&lt;strong&gt;仅在 WebWorker 中使用不能在主线程上使用&lt;/strong&gt;。这是因为返回的&lt;code&gt;AccessHandle&lt;/code&gt;的所有操作都是&lt;strong&gt;非异步&lt;/strong&gt;的，因此会阻塞 JavaScript 进程，所以你不想在主线程上执行并阻塞一切。&lt;/p&gt; &lt;h2&gt;存储大小限制&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Cookies 的大小限制在约 4 KB 的数据内，位于 RFC-6265 中。由于存储的 Cookies 会随着每个 HTTP 请求发送到服务器，因此这种限制是合理的。您可以在&lt;a href="http://www.ruslog.com/tools/cookies.html" target="_blank"&gt;这里&lt;/a&gt;测试您浏览器的 Cookies 限制。请注意，您永远不应该填满您 Cookies 的全部 4 KB，因为您的 web 服务器不会接受太长的头部信息，并会拒绝带有 &lt;code&gt;HTTP ERROR 431 - Request header fields too large&lt;/code&gt; 的请求。一旦达到这一点，您甚至无法为用户提供更新的 JavaScript 来清理 Cookies，您将无法访问该用户，直到手动清理 Cookies 为止。&lt;/li&gt; &lt;li&gt;LocalStorage 的大小限制因浏览器而异，但通常每个源的大小在 4MB 到 10MB 之间。您可以在&lt;a href="https://arty.name/localstorage.html" target="_blank"&gt;这里&lt;/a&gt;测试您的 localStorage 大小限制。 &lt;ul&gt; &lt;li&gt;Chrome/Chromium/Edge：每个域名 5 MB&lt;/li&gt; &lt;li&gt;Firefox：每个域名 10 MB&lt;/li&gt; &lt;li&gt;Safari：每个域名 4-5 MB（版本之间略有差异）&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt;没有像 localStorage 那样的特定固定大小限制。IndexedDB 的最大存储大小取决于浏览器的实现。上限通常基于用户设备上的可用磁盘空间。在 Chromium 浏览器中，它可以使用高达 80%的总磁盘空间。您可以通过调用 &lt;code&gt;await navigator.storage.estimate()&lt;/code&gt; 来获取存储大小限制的估计。通常，您可以存储数 GB 的数据，您可以在&lt;a href="https://demo.agektmr.com/storage/" target="_blank"&gt;这里&lt;/a&gt;尝试。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;OPFS&lt;/strong&gt; 具有与 IndexedDB 相同的存储大小限制。其限制取决于可用磁盘空间。这也可以在这里&lt;a href="https://demo.agektmr.com/storage/" target="_blank"&gt;测试&lt;/a&gt;。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;性能比较&lt;/h2&gt; &lt;p&gt;现在我们已经审查了每种存储方法的特性，让我们深入了解性能比较，重点关注初始化时间、读写延迟和批量操作。&lt;/p&gt; &lt;p&gt;请注意，我们只运行简单的测试，并且对于您在应用程序中的特定用例，结果可能会有所不同。此外，我们只在谷歌 Chrome（版本 128.0.6613.137）中比较性能。Firefox 和 Safari 有类似但并不完全相同的性能模式。您可以从这个&lt;a href="https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm" target="_blank"&gt;GitHub 仓库&lt;/a&gt;自行在您的机器上运行测试。对于所有测试，我们将网络速度限制为平均德国互联网速度。（下载：135,900 kbit/s，上传：28,400 kbit/s，延迟：125ms）。此外，所有测试都存储一个“平均”的 JSON 对象，根据存储可能需要将其转换为字符串。我们还只测试通过 id 存储文档的性能，因为某些技术（cookies、OPFS 和 localStorage）不支持索引范围操作，所以比较这些技术的性能没有意义。&lt;/p&gt; &lt;h3&gt;初始化时间&lt;/h3&gt; &lt;p&gt;在您存储任何数据之前，许多 API 需要设置过程，例如创建数据库、启动 WebAssembly 进程或下载额外内容。为了确保您的应用启动速度快，初始化时间很重要。&lt;/p&gt; &lt;p&gt;localStorage 和 Cookies 的 API 无需设置过程，可直接使用。IndexedDB 需要打开一个数据库及其内部的存储。WASM SQLite 需要下载 WASM 文件并处理它。OPFS 需要下载并启动一个工作文件，初始化虚拟文件系统目录。&lt;/p&gt; &lt;p&gt;这里是从第一次能够存储数据所需时间的时间测量结果：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;&lt;strong&gt;技术&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;时间（毫秒）&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;IndexedDB&lt;/td&gt;&lt;td&gt;46&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS Main Thread&lt;/td&gt;&lt;td&gt;23&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS WebWorker&lt;/td&gt;&lt;td&gt;26.8&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (memory)&lt;/td&gt;&lt;td&gt;504&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (IndexedDB)&lt;/td&gt;&lt;td&gt;535&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;这里我们可以注意到一些事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;打开一个包含单个存储的新的 IndexedDB 数据库需要相当长的时间&lt;/li&gt; &lt;li&gt;主线程向 WebWorker OPFS 发送数据的延迟开销大约为 4 毫秒。在这里，我们只发送最小数据以初始化 OPFS 文件处理器。如果处理更多数据时延迟增加，那将很有趣。&lt;/li&gt; &lt;li&gt;下载并解析 WASM SQLite 以及创建单个表大约需要半秒钟。使用 IndexedDB VFS 存储数据持久化额外增加了 31 毫秒。启用缓存并已准备好的表重新加载页面会更快，大约 420 毫秒（内存）。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;小写延迟&lt;/h3&gt; &lt;p&gt;接下来，让我们测试小写操作的延迟。当你进行许多相互独立的小数据更改时，这很重要。比如当你从 WebSocket 流数据或持久化伪随机发生的事件，如鼠标移动时。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;时间（毫秒）&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Cookies&lt;/td&gt;&lt;td&gt;0.058&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LocalStorage&lt;/td&gt;&lt;td&gt;0.017&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;IndexedDB&lt;/td&gt;&lt;td&gt;0.17&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS Main Thread&lt;/td&gt;&lt;td&gt;1.46&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS WebWorker&lt;/td&gt;&lt;td&gt;1.54&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (memory)&lt;/td&gt;&lt;td&gt;0.17&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (IndexedDB)&lt;/td&gt;&lt;td&gt;3.17&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;这里我们可以注意到一些事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;LocalStorage 具有最低的写入延迟，每次写入仅需 0.017 毫秒。&lt;/li&gt; &lt;li&gt;IndexedDB 的写入速度比 localStorage 慢约 10 倍。&lt;/li&gt; &lt;li&gt;发送数据到 WASM SQLite 进程并通过 IndexedDB 持久化，每次写入超过 3 毫秒都很慢。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;OPFS 操作将 JSON 数据写入一个文档大约需要 1.5 毫秒。我们可以看到，首先将数据发送到 webworker 会稍微慢一些，这源于在双方序列化和反序列化数据时的开销。如果我们不是为每个文档创建一个 OPFS 文件，而是将所有内容追加到单个文件中，性能模式将发生显著变化。然后，从&lt;code&gt;createSyncAccessHandle()&lt;/code&gt;创建的更快文件句柄每次写入只需大约 1 毫秒。但这需要以某种方式记住每个文档存储的位置。因此，在我们的测试中，我们将继续使用每个文档一个文件的方式。&lt;/p&gt; &lt;h3&gt;小读操作延迟&lt;/h3&gt; &lt;p&gt;现在我们已经存储了一些文档，让我们通过它们的&lt;code&gt;id&lt;/code&gt;来测量读取单个文档所需的时间。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;时间（毫秒）&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Cookies&lt;/td&gt;&lt;td&gt;0.132&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LocalStorage&lt;/td&gt;&lt;td&gt;0.0052&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;IndexedDB&lt;/td&gt;&lt;td&gt;0.1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS Main Thread&lt;/td&gt;&lt;td&gt;1.28&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS WebWorker&lt;/td&gt;&lt;td&gt;1.41&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (memory)&lt;/td&gt;&lt;td&gt;0.45&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (IndexedDB)&lt;/td&gt;&lt;td&gt;2.93&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;这里我们可以注意到一些事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;LocalStorage 的读取速度非常快，每次读取仅需 0.0052 毫秒。&lt;/li&gt; &lt;li&gt;其他技术以与它们的写入延迟相似的速度执行读取操作。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;大量写入&lt;/h3&gt; &lt;p&gt;下一步，让我们一次性处理 200 份文档的大批量操作。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;时间（毫秒）&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Cookies&lt;/td&gt;&lt;td&gt;20.6&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LocalStorage&lt;/td&gt;&lt;td&gt;5.79&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;IndexedDB&lt;/td&gt;&lt;td&gt;13.41&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS Main Thread&lt;/td&gt;&lt;td&gt;280&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS WebWorker&lt;/td&gt;&lt;td&gt;104&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (memory)&lt;/td&gt;&lt;td&gt;19.1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (IndexedDB)&lt;/td&gt;&lt;td&gt;37.12&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;这里我们可以注意到一些事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;将数据发送到 WebWorker 并通过更快的 OPFS API 运行，大约快两倍。&lt;/li&gt; &lt;li&gt;WASM SQLite 在批量操作方面比其单次写入延迟表现更好。这是因为如果一次发送数据到 WASM 和反向传输，比每次处理一个文档要快。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;大量读取&lt;/h3&gt; &lt;p&gt;现在让我们一次性读取 100 份文档。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;时间（毫秒）&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Cookies&lt;/td&gt;&lt;td&gt;6.34&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LocalStorage&lt;/td&gt;&lt;td&gt;0.39&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;IndexedDB&lt;/td&gt;&lt;td&gt;4.99&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS Main Thread&lt;/td&gt;&lt;td&gt;54.79&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;OPFS WebWorker&lt;/td&gt;&lt;td&gt;25.61&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (memory)&lt;/td&gt;&lt;td&gt;3.59&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;WASM SQLite (IndexedDB)&lt;/td&gt;&lt;td&gt;5.84 (35ms without cache)&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;这里我们可以注意到一些事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;在 OPFS webworker 中读取许多文件的速度比较慢的主线程模式快约两倍。&lt;/li&gt; &lt;li&gt;WASM SQLite 非常快。进一步检查显示，WASM SQLite 进程将文档保存在内存缓存中，这提高了我们在写入相同数据后直接读取时的延迟。当在写入和读取之间重新加载浏览器标签时，找到 100 份文档需要大约 &lt;strong&gt;35 毫秒&lt;/strong&gt;。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;性能结论&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;本地存储真的很快，但请记住它也有一些缺点： &lt;ul&gt; &lt;li&gt;它阻塞了主要的 JavaScript 进程，因此不应用于大量操作。&lt;/li&gt; &lt;li&gt;只有键值赋值是可能的，当你需要基于索引的范围内查询数据时，无法高效地使用它。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;OPFS 在 WebWorker 中使用时，与在主线程中直接使用相比，使用&lt;code&gt;createSyncAccessHandle()&lt;/code&gt;方法要快得多。&lt;/li&gt; &lt;li&gt;SQLite WASM 可以很快，但你最初必须下载完整的二进制文件并启动它，这大约需要半秒钟。如果你的应用程序只启动一次并且长时间使用，这可能根本无关紧要。但对于在许多浏览器标签中多次打开和关闭的 Web 应用程序，这可能会成为一个问题。&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Tue, 03 Dec 2024 01:30:50 GMT</pubDate>
    </item>
    <item>
      <title>MySQL 5.7 - 通过 BINLOG 恢复数据</title>
      <link>https://maruifu.cn/article/328</link>
      <content:encoded>&lt;p&gt;日常开发，运维中，经常会出现误删数据的情况。误删数据的类型大致可分为以下几类：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用 delete 误删行&lt;/li&gt; &lt;li&gt;使用 drop table 或 truncate table 误删表&lt;/li&gt; &lt;li&gt;使用 drop database 语句误删数据库&lt;/li&gt; &lt;li&gt;使用 rm 命令误删整个 MySQL 实例。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;不同的情况，都会有其优先的解决方案：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;针对误删行，可以通过 Flashback 工具将数据恢复&lt;/li&gt; &lt;li&gt;针对误删表或库，一般采用通过 BINLOG 将数据恢复。&lt;/li&gt; &lt;li&gt;而对于误删 MySQL 实例，则需要我们搭建 HA 的 MySQL 集群，并保证我们的数据跨机房，跨城市保存。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;本篇主要讨论的内容是误删表或者库，会先介绍有关 BINLOG 的操作命令，然后会对误删表的这种情况进行实际的模拟。&lt;/p&gt; &lt;h2&gt;BINLOG 常见操作命令&lt;/h2&gt; &lt;p&gt;BINLOG 的查询方式一般分为两种，一种是进入 MySQL 控制台进行查询，另一种是通过 MySQL 提供的工具 mysqlbinlog 进行查询，两者的不同会在下面介绍。&lt;/p&gt; &lt;h3&gt;通过 MySQL Cli 查询 BINLOG 信息&lt;/h3&gt; &lt;p&gt;在 cli 中，常见的命令如下：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;# 查询 BINLOG 格式 show VARIABLES like 'binlog_format';  # 查询 BINLOG 位置 show VARIABLES like 'datadir';  # 查询当前数据库中 BINLOG 名称及大小 show binary logs;  # 查看 master 正在写入的 BINLOG 信息 show master status\G;  # 通过 offset 查看 BINLOG 信息 show BINLOG events in 'mysql-bin.000034' limit 9000,  10;  # 通过 position 查看 binlog 信息 show BINLOG events in 'mysql-bin.000034' from 1742635 limit 10; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用 &lt;code&gt;show BINLOG events&lt;/code&gt; 的问题：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用该命令时，如果当前 binlog 文件很大，而且没有指定 &lt;code&gt;limit&lt;/code&gt;，会引发对资源的过度消耗。因为 MySQL 客户端需要将 binlog 的全部内容处理，返回并显示出来。为了防止这种情况，mysqlbinlog 工具是一个很好的选择。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;通过 mysqlbinlog 查询 BINLOG 信息&lt;/h3&gt; &lt;p&gt;在介绍 mysqlbinlog 工具使用前，先来看下 BINLOG 文件的内容：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-perl"&gt;mysqlbinlog  --no-defaults mysql-bin.000034 | less # at 141 #100309  9:28:36 server id 123  end_log_pos 245   Query thread_id=3350  exec_time=11  error_code=0 &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;at&lt;/code&gt; 表示 offset 或者说事件开始的起始位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;100309 9:28:36 server id 123&lt;/code&gt; 表示 server 123 开始执行事件的日期&lt;/li&gt; &lt;li&gt;&lt;code&gt;end_log_pos 245&lt;/code&gt; 表示事件的结束位置 + 1，或者说是下一个事件的起始位置。&lt;/li&gt; &lt;li&gt;&lt;code&gt;exec_time&lt;/code&gt; 表示在 master 上花费的时间，在 salve 上，记录的时间是从 Master 记录开始，一直到 Slave 结束完成所花费的时间。&lt;/li&gt; &lt;li&gt;&lt;code&gt;rror_code=0&lt;/code&gt; 表示没有错误发生。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;在大致了解 binlog 的内容后，mysqlbinlog 的用途有哪些？：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;mysqlbinlog 可以作为代替 cli 读取 binlog 的工具。&lt;/li&gt; &lt;li&gt;mysqlbinlog 可以将执行过的 SQL 语句输出，用于数据的恢复或备份。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;查询 BINLOG 日志：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-haskell"&gt;# 查询规定时候后发生的 BINLOG 日志 mysqlbinlog --no-defaults --base64-output=decode-rows -v \  --start-datetime  &amp;quot;2019-11-22 14:00:00&amp;quot; \  --database sync_test  mysql-bin.000034 | less &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;导出 BINLOG 日志，用于分析和排查 sql 语句：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;mysqlbinlog --no-defaults --base64-output=decode-rows -v \  --start-datetime  &amp;quot;2019-11-22 14:00:00&amp;quot; \  --database sync_test \  mysql-bin.000034 &amp;gt; /home/mysql_backup/binlog_raw.sql &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;导入 BINLOG 日志&lt;/p&gt; &lt;pre&gt;&lt;code class="language-haskell"&gt;# 通过 BINLOG 进行恢复。 mysqlbinlog --start-position=1038 --stop-position=1164 \  --database=db_name  mysql-bin.000034 | \  mysql  -u cisco -p db_name  # 通过 BINLOG 导出的 sql 进行恢复。 mysql -u cisco -p db_name &amp;lt; binlog_raw.sql &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;mysqlbinlog 的常用参数：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--database&lt;/code&gt; 仅仅列出配置的数据库信息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--no-defaults&lt;/code&gt; 读取没有选项的文件, 指定的原因是由于 mysqlbinlog 无法识别 BINLOG 中的 &lt;code&gt;default-character-set=utf8&lt;/code&gt; 指令&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--offset&lt;/code&gt; 跳过 log 中 N 个条目&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--verbose&lt;/code&gt; 将日志信息重建为原始的 SQL 陈述。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;-v&lt;/code&gt; 仅仅解释行信息&lt;/li&gt; &lt;li&gt;&lt;code&gt;-vv&lt;/code&gt; 不但解释行信息，还将 SQL 列类型的注释信息也解析出来&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--start-datetime&lt;/code&gt;显示从指定的时间或之后的时间的事件。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;接收 &lt;code&gt;DATETIME&lt;/code&gt; 或者 &lt;code&gt;TIMESTRAMP&lt;/code&gt; 格式。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;--base64-output=decode-rows&lt;/code&gt;将 BINLOG 语句中事件以 base-64 的编码显示，对一些二进制的内容进行屏蔽。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;AUTO&lt;/code&gt; 默认参数，自动显示 BINLOG 中的必要的语句&lt;/li&gt; &lt;li&gt;&lt;code&gt;NEVER&lt;/code&gt; 不会显示任何的 BINLOG 语句，如果遇到必须显示的 BINLOG 语言，则会报错退出。&lt;/li&gt; &lt;li&gt;&lt;code&gt;DECODE-ROWS&lt;/code&gt; 显示通过 &lt;code&gt;-v&lt;/code&gt; 显示出来的 SQL 信息，过滤到一些 BINLOG 二进制数据。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;MySQL Cli 和 mysqlbinlog 工具之间的比较&lt;a href="chrome-extension://pcfbfimijgibligmbglggnbiobgjgmbk/simplify.html?origin=https://www.cnblogs.com#mysql-cli-和-mysqlbinlog-工具之间的比较" target="_blank"&gt;#&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;如果想知道当前 MySQL 中正在写入的 BINLOG 的名称，大小等基本信息时，可以通过 Cli 相关的命令来查询。&lt;/p&gt; &lt;p&gt;但想查询，定位，恢复 BINLOG 中具体的数据时，要通过 mysqlbinlog 工具，因为相较于 Cli 来说，mysqlbinlog 提供了 &lt;code&gt;--start-datetime&lt;/code&gt;，&lt;code&gt;--stop-position&lt;/code&gt; 等这样更为丰富的参数供我们选择。这时 Cli 中 &lt;code&gt;SHOW BINLOG EVENTS&lt;/code&gt; 的简要语法就变得相形见绌了。&lt;/p&gt; &lt;h2&gt;使用 BINLOG 恢复数据&lt;/h2&gt; &lt;p&gt;恢复的大致流程如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;ol&gt; &lt;li&gt;会创建数据库和表，并插入数据。&lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;li&gt; &lt;ol start="2"&gt; &lt;li&gt;误删一条数据。&lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;li&gt; &lt;ol start="3"&gt; &lt;li&gt;继续插入数据。&lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;li&gt; &lt;ol start="4"&gt; &lt;li&gt;误删表。&lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;li&gt; &lt;ol start="5"&gt; &lt;li&gt;最后将原来以及之后插入的数据进行恢复。&lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;准备数据&lt;/h3&gt; &lt;p&gt;准备数据库，表及数据：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;# 创建临时数据库 CREATE DATABASE IF NOT EXISTS test_binlog \ default charset utf8 COLLATE utf8_general_ci;    # 创建临时表 CREATE TABLE `sync_test` (`id` int(11) \ NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL,  \ PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  # 添加数据 insert into sync_test (id, name) values (null, 'xiaoa'); insert into sync_test (id, name) values (null, 'xiaob'); insert into sync_test (id, name) values (null, 'xiaoc');  # 查看添加的数据 select * from sync_test;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;删除表或者数据&lt;/h3&gt; &lt;p&gt;误删操作：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;# 删除 name=xiaob 的数据 delete from sync_test where id=3  # 插入几条数据 insert into sync_test (id, name) values (null, 'xiaod'); insert into sync_test (id, name) values (null, 'xiaoe'); insert into sync_test (id, name) values (null, 'xiaof');  # 删除表 DROP TABLE sync_test; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;数据的恢复&lt;/h3&gt; &lt;p&gt;在执行数据恢复前，如果操作的是生产环境，会有如下的建议：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用 &lt;code&gt;flush logs&lt;/code&gt; 命令，替换当前主库中正在使用的 binlog 文件，好处如下： &lt;ul&gt; &lt;li&gt;可将误删操作，定位在一个 BINLOG 文件中，便于之后的数据分析和恢复。&lt;/li&gt; &lt;li&gt;避免操作正在被使用的 BINLOG 文件，防止发生意外情况。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;数据的恢复不要在生产库中执行，先在临时库恢复，确认无误后，再倒回生产库。防止对数据的二次伤害。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;通常来说，恢复主要有两个步骤：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;在临时库中，恢复定期执行的全量备份数据。&lt;/li&gt; &lt;li&gt;然后基于全量备份的数据点，通过 BINLOG 来恢复误操作和正常的数据。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;使用 BINLOG 做数据恢复前：&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-mipsasm"&gt;# 查看正在使用的 Binlog 文件 show master status\G; # 显示结果是： mysql-bin.000034  # 执行 flush logs 操作，生成新的 BINLOG flush logs;  # 查看正在使用的 Binlog 文件 show master status\G; # 结果是：mysql-bin.000035  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;确定恢复数据的步骤：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;这里主要是有两条误删的操作，数据行的误删和表的误删。有两种方式进行恢复。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;方式一：首先恢复到删除表操作之前的位置，然后再单独恢复误删的数据行。&lt;/li&gt; &lt;li&gt;方式二：首先恢复到误删数据行的之前的位置，然后跳过误删事件再恢复数据表操作之前的位置。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这里采用方式一的方案进行演示，由于是演示，就不额外找一个临时库进行全量恢复了，直接进行操作。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查询创建表的事件位置和删除表的事件位置&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-haskell"&gt;#  根据时间确定位置信息 mysqlbinlog --no-defaults --base64-output=decode-rows -v \  --start-datetime  &amp;quot;2019-11-22 14:00:00&amp;quot; \  --database test_binlog  mysql-bin.000034 | less  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;创建表的开始位置：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img2018.cnblogs.com/blog/1861307/201911/1861307-20191124181830281-1061896540.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/1861307-20191124181830281-1061896540.png" alt="创建表的开始位置" title="创建表的开始位置" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;删除表的结束位置：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img2018.cnblogs.com/blog/1861307/201911/1861307-20191124181841418-936533762.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/1861307-20191124181841418-936533762.png" alt="删除表的结束位置" title="删除表的结束位置" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;插入 name='xiaob' 的位置：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img2018.cnblogs.com/blog/1861307/201911/1861307-20191124181849655-1021216310.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/1861307-20191124181849655-1021216310.png" alt="插入 name='xiaob' 的位置" title="插入 name='xiaob' 的位置" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-perl"&gt;# 根据位置导出 SQL 文件 mysqlbinlog --no-defaults --base64-output=decode-rows -v \  --start-position &amp;quot;2508132&amp;quot; --stop-position &amp;quot;2511004&amp;quot; \  --database test_binlog  mysql-bin.000034 \  &amp;gt; /home/mysql_backup/test_binlog_step1.sql     mysqlbinlog --no-defaults --base64-output=decode-rows -v \  --start-position &amp;quot;2508813&amp;quot; --stop-position &amp;quot;2509187&amp;quot; \  --database test_binlog  mysql-bin.000034 \  &amp;gt; /home/mysql_backup/test_binlog_step2.sql    # 使用 mysql 进行恢复 mysql -u cisco -p &amp;lt; /home/mysql_backup/test_binlog_step1.sql mysql -u cisco -p &amp;lt; /home/mysql_backup/test_binlog_step2.sql  &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;MySQL 5.7 中无论是否打开 GTID 的配置，在每次事务开启时，都首先会出 GTID 的一个事务，用于并行复制。所以在确定导出开始事务位置时，要算上这个事件。&lt;/p&gt; &lt;p&gt;在使用 --stop-position 导出时，会导出在指定位置的前一个事件，所以这里要推后一个事务。&lt;/p&gt; &lt;p&gt;对于 DML 的语句，主要结束位置要算上 COMMIT 的位置。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;在文章开始时，我们熟悉了操作 BINLOG 的两种方式 CLI 和 mysqlbinlog 工具，接着介绍了其间的区别和使用场景，对于一些大型的 BINLOG 文件，使用 mysqlbinlog 会更加的方便和效率。并对 mysqlbinlog 的一些常见参数进行了介绍。&lt;/p&gt; &lt;p&gt;接着通过使用 mysqlbinlog 实际模拟了数据恢复的过程，并在恢复数据时，提出了一些需要注意的事项，比如 &lt;code&gt;flush logs&lt;/code&gt; 等。&lt;/p&gt; &lt;p&gt;最后在恢复数据时，要注意 &lt;code&gt;start-position&lt;/code&gt; 和 &lt;code&gt;end-position&lt;/code&gt; 的一些小细节，来保证找到合适的位置。&lt;/p&gt; &lt;h2&gt;参考&lt;/h2&gt; &lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/point-in-time-recovery.html" target="_blank"&gt;point-in-time-recovery&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/recovery-from-backups.html" target="_blank"&gt;recovery-from-backups&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/backup-policy.html" target="_blank"&gt;backup-policy.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;转自: &lt;a href="https://www.cnblogs.com/michael9/p/11923483.html#%E4%BD%BF%E7%94%A8-binlog-%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE" target="_blank"&gt;Ez啊&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 18 Nov 2024 08:04:00 GMT</pubDate>
    </item>
    <item>
      <title>Mac电脑无法将U盘格式化(抹除)为APFS格式的解决</title>
      <link>https://maruifu.cn/article/327</link>
      <content:encoded>&lt;h2&gt;概览&lt;/h2&gt; &lt;p&gt;很多小伙伴把新买的U盘插在Mac上，然后想把它格式化（抹除）为APFS格式。但却只能选择旧的Mac OS 扩展类型格式，压根看不到APFS格式的选项&lt;/p&gt; &lt;p&gt;如图所示，在U盘抹除中根本看不到 APFS 格式。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/image-20241118153726421.png" alt="image-20241118153726421" title="image-20241118153726421" /&gt;&lt;/p&gt; &lt;h2&gt;APFS 格式的优势&lt;/h2&gt; &lt;p&gt;APFS 是 Apple File System 的简称，它最早发布于2016年6月，实际在2017年发布的 macOS High Sierra 10.13 开发者预览版中首先启用，用来替代旧 HFS+ 文件系统。&lt;/p&gt; &lt;p&gt;APFS格式的特点是： “优化闪存/SSD存储，并以加密为主要功能”，在I/O联合上使用了“独特的copy-on-write设计”，在确保可靠性的基础上优化性能。&lt;/p&gt; &lt;p&gt;一句话总结：APFS 格式与其前代格式相比具有许多优势，它已针对SSD和闪存驱动器进行了优化，最适合闪存和固态驱动器。&lt;/p&gt; &lt;p&gt;可以看到，使用 APFS 格式在Mac系统上可以最高效的操作U盘。&lt;/p&gt; &lt;h2&gt;原因&lt;/h2&gt; &lt;p&gt;之所以在格式化（抹除）操作中看不到 APFS 格式，是因为当前U盘分区表格式是主引导记录（MBR）格式。&lt;/p&gt; &lt;p&gt;注意，该格式是U盘分区表的格式，而不是格式化的格式。&lt;/p&gt; &lt;p&gt;解决起来很简单，我们只需将U盘分区表格式设置为GPT格式（GUID分区表）即可&lt;/p&gt; &lt;h2&gt;设置&lt;/h2&gt; &lt;p&gt;首先，插上U盘，打开Mac系统中自带的 磁盘工具 程序，选择 显示所有设备&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/image-20241118153940129.png" alt="image-20241118153940129" title="image-20241118153940129" /&gt;&lt;/p&gt; &lt;p&gt;接着，选择整个U盘设备（左侧“外置”菜单里最顶层的项目），而不是选择其中的容器或卷：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/image-20241118154019020.png" alt="image-20241118154019020" title="image-20241118154019020" /&gt;&lt;/p&gt; &lt;p&gt;然后，点击程序顶部的 抹除 按钮，在弹出的窗口中的分区方案里选择 GUID 分区图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/image-20241118154032682.png" alt="image-20241118154032682" title="image-20241118154032682" /&gt;&lt;/p&gt; &lt;p&gt;现在，可以在格式里选择 APFS 格式了：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/11/18/image-20241118154047978.png" alt="image-20241118154047978" title="image-20241118154047978" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 18 Nov 2024 07:42:47 GMT</pubDate>
    </item>
    <item>
      <title>Parallels Desktop 给Linux增加磁盘大小</title>
      <link>https://maruifu.cn/article/326</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;默认安装镜像只分配了64G ,安装之前还不能修改硬盘大小&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;修改虚拟机分配的大小&lt;/h2&gt; &lt;p&gt;设置-&amp;gt;硬盘&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/10/31/image-20241031140137631.png" alt="image-20241031140137631" title="image-20241031140137631" /&gt;&lt;/p&gt; &lt;h2&gt;列出块设备信息&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# lsblk NAME                         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS sda                            8:0    0  200G  0 disk  ├─sda1                         8:1    0  600M  0 part /boot/efi ├─sda2                         8:2    0    1G  0 part /boot └─sda3                         8:3    0 62.4G  0 part    ├─almalinux_almalinux-root 253:0    0 40.6G  0 lvm  /   ├─almalinux_almalinux-swap 253:1    0    2G  0 lvm  [SWAP]   └─almalinux_almalinux-home 253:2    0 19.8G  0 lvm  /home  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看磁盘的分区和未分配空间&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# sudo parted /dev/sda print free 型号：ATA AlmaLinux-0 SSD (scsi) 磁盘 /dev/sda：215GB 扇区大小 (逻辑/物理)：512B/4096B 分区表：gpt 磁盘标志：  编号  起始点  结束点  大小    文件系统  名称                  标志       17.4kB  1049kB  1031kB  可用空间  1    1049kB  630MB   629MB   fat32     EFI System Partition  启动, esp  2    630MB   1704MB  1074MB  xfs  3    1704MB  68.7GB  67.0GB                                  lvm       68.7GB  215GB   146GB   可用空间 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建物理卷 (PV)&lt;/h2&gt; &lt;h3&gt;创建新分区&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;sudo parted /dev/sda &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在parted提示符下输入:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mkpart primary 68.7GB 215GB print quit &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看新分区&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# lsblk NAME                         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS sda                            8:0    0  200G  0 disk  ├─sda1                         8:1    0  600M  0 part /boot/efi ├─sda2                         8:2    0    1G  0 part /boot ├─sda3                         8:3    0 62.4G  0 part  │ ├─almalinux_almalinux-root 253:0    0 40.6G  0 lvm  / │ ├─almalinux_almalinux-swap 253:1    0    2G  0 lvm  [SWAP] │ └─almalinux_almalinux-home 253:2    0 19.8G  0 lvm  /home └─sda4                         8:4    0  136G  0 part  sr0                           11:0    1 1024M  0 rom &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;sda4 就是新分区的名字&lt;/p&gt; &lt;h3&gt;格式化新分区为物理卷&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# pvcreate /dev/sda4   Physical volume &amp;quot;/dev/sda4&amp;quot; successfully created. &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;扩展卷组以包含新的物理卷&lt;/h2&gt; &lt;h3&gt;查看当前的卷组&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# vgdisplay   --- Volume group ---   VG Name               almalinux_almalinux   System ID                Format                lvm2   Metadata Areas        1   Metadata Sequence No  6   VG Access             read/write   VG Status             resizable   MAX LV                0   Cur LV                3   Open LV               3   Max PV                0   Cur PV                1   Act PV                1   VG Size               62.41 GiB   PE Size               4.00 MiB   Total PE              15977   Alloc PE / Size       15977 / 62.41 GiB   Free  PE / Size       0 / 0      VG UUID               3b18iP-pPQ6-5iEe-91AQ-2edY-N6Py-FvzC2u &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;将新的物理卷添加到卷组&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# vgextend almalinux_almalinux /dev/sda4   Volume group &amp;quot;almalinux_almalinux&amp;quot; successfully extended &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最下面146GB就是我要扩容的空间大小&lt;/p&gt; &lt;h2&gt;扩展根逻辑卷&lt;/h2&gt; &lt;h3&gt;查看现有逻辑卷&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# lvdisplay   --- Logical volume ---   LV Path                /dev/almalinux_almalinux/swap   LV Name                swap   VG Name                almalinux_almalinux   LV UUID                m2JM1L-Mgmw-m7dP-4RRg-mZJ9-CbIX-LFA8fZ   LV Write Access        read/write   LV Creation host, time AlmaLinux, 2024-10-31 10:53:43 +0800   LV Status              available   # open                 2   LV Size                1.99 GiB   Current LE             510   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:1       --- Logical volume ---   LV Path                /dev/almalinux_almalinux/home   LV Name                home   VG Name                almalinux_almalinux   LV UUID                eA0RJ4-2s4J-tb1F-3ivQ-cCVd-CBxw-EdmwX4   LV Write Access        read/write   LV Creation host, time AlmaLinux, 2024-10-31 10:53:44 +0800   LV Status              available   # open                 1   LV Size                19.82 GiB   Current LE             5074   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:2       --- Logical volume ---   LV Path                /dev/almalinux_almalinux/root   LV Name                root   VG Name                almalinux_almalinux   LV UUID                g1U1xP-j8dE-so1R-LFeA-EF40-D1O7-51XtQc   LV Write Access        read/write   LV Creation host, time AlmaLinux, 2024-10-31 10:53:44 +0800   LV Status              available   # open                 1   LV Size                &amp;lt;40.60 GiB   Current LE             10393   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:0  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;扩展根逻辑卷&lt;/h3&gt; &lt;p&gt;现在，你可以使用&lt;code&gt;lvextend&lt;/code&gt;命令来扩展根逻辑卷&lt;code&gt;almalinux_almalinux-root&lt;/code&gt;，以使用新添加的空间。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# lvextend -l +100%FREE  /dev/almalinux_almalinux/root   Size of logical volume almalinux_almalinux/root changed from &amp;lt;40.60 GiB (10393 extents) to 176.59 GiB (45208 extents).   Logical volume almalinux_almalinux/root successfully resized.  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;调整文件系统大小&lt;/h2&gt; &lt;h3&gt;查看文件系统类型&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# df -T 文件系统                             类型         1K-块    已用      可用 已用% 挂载点 devtmpfs                             devtmpfs      4096       0      4096    0% /dev tmpfs                                tmpfs      8006968       0   8006968    0% /dev/shm tmpfs                                tmpfs      3202788    9676   3193112    1% /run /dev/mapper/almalinux_almalinux-root xfs      109608960 6012672 103596288    6% / /dev/mapper/almalinux_almalinux-home xfs       20717568  185280  20532288    1% /home /dev/sda2                            xfs         983040  276528    706512   29% /boot /dev/sda1                            vfat        613160    7196    605964    2% /boot/efi tmpfs                                tmpfs      1601392     136   1601256    1% /run/user/1000 tmpfs                                tmpfs      1601392      36   1601356    1% /run/user/0 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;调整文件系统大小&lt;/h3&gt; &lt;p&gt;扩展文件系统以使用新增加的空间。假设你使用的是 &lt;code&gt;ext4&lt;/code&gt; 文件系统：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;resize2fs /dev/almalinux_almalinux/root &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果使用其他文件系统，比如 &lt;code&gt;xfs&lt;/code&gt;，可以用：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;[root@AlmaLinux ~]# xfs_growfs / meta-data=/dev/mapper/almalinux_almalinux-root isize=512    agcount=18, agsize=2660608 blks          =                       sectsz=4096  attr=2, projid32bit=1          =                       crc=1        finobt=1, sparse=1, rmapbt=0          =                       reflink=1    bigtime=1 inobtcount=1 nrext64=0 data     =                       bsize=4096   blocks=46292992, imaxpct=25          =                       sunit=0      swidth=0 blks naming   =version 2              bsize=4096   ascii-ci=0, ftype=1 log      =internal log           bsize=4096   blocks=16384, version=2          =                       sectsz=4096  sunit=1 blks, lazy-count=1 realtime =none                   extsz=4096   blocks=0, rtextents=0  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;成功扩容&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@AlmaLinux ~]# lsblk NAME                         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS sda                            8:0    0   200G  0 disk  ├─sda1                         8:1    0   600M  0 part /boot/efi ├─sda2                         8:2    0     1G  0 part /boot ├─sda3                         8:3    0  62.4G  0 part  │ ├─almalinux_almalinux-root 253:0    0 176.6G  0 lvm  / │ ├─almalinux_almalinux-swap 253:1    0     2G  0 lvm  [SWAP] │ └─almalinux_almalinux-home 253:2    0  19.8G  0 lvm  /home └─sda4                         8:4    0   136G  0 part    └─almalinux_almalinux-root 253:0    0 176.6G  0 lvm  / &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 31 Oct 2024 06:47:00 GMT</pubDate>
    </item>
    <item>
      <title>大语言模型（AI）接入小爱音箱</title>
      <link>https://maruifu.cn/article/325</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;家里闲置了一个小米 mini 音箱，打算接入大模型试试,,从“人工智障”秒变学霸。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;🔊 支持的小爱音箱型号&lt;/h2&gt; &lt;h3&gt;✅ 完美运行&lt;/h3&gt; &lt;p&gt;已知可以完美运行 &lt;code&gt;MiGPT&lt;/code&gt; 的小爱音箱型号有：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;型号&lt;/th&gt;&lt;th&gt;ttsCommand&lt;/th&gt;&lt;th&gt;wakeUpCommand&lt;/th&gt;&lt;th&gt;playingCommand&lt;/th&gt;&lt;th&gt;streamResponse&lt;/th&gt;&lt;th&gt;反馈来源&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 Pro&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-lx06:2" target="_blank"&gt;LX06&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop" target="_blank"&gt;@idootop&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 mini&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-lx01:1" target="_blank"&gt;LX01&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[4, 1, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/92#issuecomment-2168013500" target="_blank"&gt;@gsscsd&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 Play（2019 款）&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-lx05:1" target="_blank"&gt;LX05&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[3, 1, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/92#issuecomment-2168424538" target="_blank"&gt;@wt666666&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 万能遥控版&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-lx5a:2" target="_blank"&gt;LX5A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/62" target="_blank"&gt;@imhsz&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小米 AI 音箱&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-s12:2" target="_blank"&gt;S12&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;微信: CMSJ&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小米 AI 音箱（第二代）&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l15a:2" target="_blank"&gt;L15A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[3, 1, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;微信: 龙之广&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱智能家庭屏 10&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-x10a:1" target="_blank"&gt;X10A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/92#issuecomment-2190928452" target="_blank"&gt;@IDarkBoss&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Xiaomi Sound Pro&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l17a:1" target="_blank"&gt;L17A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;td&gt;微信: eof&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;🚗 正常运行&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;部分机型的 MIoT 接口不支持查询设备播放状态或查询状态异常，比如小米音箱 Play 增强版（L05C），将会导致 &lt;code&gt;MiGPT&lt;/code&gt; 部分功能异常，无法使用连续对话等，此时需要关闭 &lt;code&gt;streamResponse&lt;/code&gt;。相关 &lt;a href="https://github.com/idootop/mi-gpt/issues/14" target="_blank"&gt;issue&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;可以正常运行 &lt;code&gt;MiGPT&lt;/code&gt;，但不支持连续对话的小爱音箱型号有：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;型号&lt;/th&gt;&lt;th&gt;ttsCommand&lt;/th&gt;&lt;th&gt;wakeUpCommand&lt;/th&gt;&lt;th&gt;playingCommand&lt;/th&gt;&lt;th&gt;streamResponse&lt;/th&gt;&lt;th&gt;反馈来源&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;小爱音箱&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l06a:2" target="_blank"&gt;L06A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/42" target="_blank"&gt;@zhanglc&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 Play&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l05b:1" target="_blank"&gt;L05B&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/48" target="_blank"&gt;@BiuBiu2323&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小米小爱音箱 Play 增强版&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l05c:1" target="_blank"&gt;L05C&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/14" target="_blank"&gt;@lyddias&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Xiaomi 智能家庭屏 6&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-x6a:1" target="_blank"&gt;X6A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/80" target="_blank"&gt;@Hongwing&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Redmi 小爱触屏音箱 Pro 8 英寸&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-x08e:1" target="_blank"&gt;X08E&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 3]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[7, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/20" target="_blank"&gt;@shangjiyu&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱音箱 Art&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-l09a:1" target="_blank"&gt;L09A&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[3, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[3, 2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/92#issuecomment-2181944065" target="_blank"&gt;@zwsn&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小爱触屏音箱&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device🔈0000A015:xiaomi-lx04:2" target="_blank"&gt;LX04&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[5, 2]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/92#issuecomment-2184678990" target="_blank"&gt;@ilovesouthpark&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;❌ 不支持&lt;/h3&gt; &lt;p&gt;完全不支持 &lt;code&gt;MiGPT&lt;/code&gt; 的小爱音箱型号有：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;型号&lt;/th&gt;&lt;th&gt;反馈来源&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;小米小爱音箱 HD&lt;/td&gt;&lt;td&gt;&lt;a href="https://home.miot-spec.com/spec/onemore.wifispeaker.sm4" target="_blank"&gt;SM4&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/idootop/mi-gpt/issues/91" target="_blank"&gt;@romantech&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;小米小爱蓝牙音箱随身版&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;微信: 明天&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;🔥 型号分享&lt;/h3&gt; &lt;p&gt;如果你是其他型号的小爱音箱，欢迎把你的型号和配置参数分享给大家，分享格式如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;名称：小爱音箱 Pro&lt;/li&gt; &lt;li&gt;型号：LX06&lt;/li&gt; &lt;li&gt;ttsCommand：[5, 1]&lt;/li&gt; &lt;li&gt;wakeUpCommand：[5, 3]&lt;/li&gt; &lt;li&gt;playingCommand：未设置&lt;/li&gt; &lt;li&gt;streamResponse：true（支持连续对话）&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;环境配置&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Python 3.8以上的版本 （ https://www.python.org/）&lt;/li&gt; &lt;li&gt;nodeJS （https://nodejs.org/zh-cn）&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;准备小米音箱&lt;/h2&gt; &lt;h3&gt;查看小米音箱绑定的名称&lt;/h3&gt; &lt;p&gt;我的就是&lt;code&gt;小爱音箱mini&lt;/code&gt;，&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/10/23/image-20241023233152712.png" alt="image-20241023233152712" title="image-20241023233152712" /&gt;&lt;/p&gt; &lt;h3&gt;查看小米音箱的型号&lt;/h3&gt; &lt;p&gt;我的型号是&lt;code&gt;LX01&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/10/23/image-20241023232411991.png" alt="image-20241023232411991" title="image-20241023232411991" /&gt;&lt;/p&gt; &lt;h3&gt;查看小米账号&lt;/h3&gt; &lt;p&gt;小爱音箱 APP--&amp;gt;我--&amp;gt;点击头像--&amp;gt;查看小米账号--&amp;gt;我的是 &lt;code&gt;167857068&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/10/23/image-20241023232824921.png" alt="image-20241023232824921" title="image-20241023232824921" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;综上得到小米音箱的名称，型号，小米账号&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;申请大模型的API接口&lt;/h2&gt; &lt;h3&gt;注册silicon&lt;/h3&gt; &lt;p&gt;silicon注册和使用地址: https://cloud.siliconflow.cn/i/bp8xtUdh&lt;/p&gt; &lt;p&gt;邀请码：bp8xtUdh（谢谢支持）&lt;/p&gt; &lt;h3&gt;新建API密钥&lt;/h3&gt; &lt;p&gt;注册登录后，单击左边栏的API密钥，单击新建API密钥&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/10/23/image-20241023233914834.png" alt="image-20241023233914834" title="image-20241023233914834" /&gt;&lt;/p&gt; &lt;p&gt;点击密钥即可复制&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我们可以调用千问2.5的这个模型，满足日常对话完全没有问题，并且是免费调用的&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;mi-gpt项目&lt;/h2&gt; &lt;h3&gt;下载 mi-gpt 项目&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;git clone https://github.com/idootop/mi-gpt.git &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;cd mi-gpt mv .env.example .env          mv .migpt.example.js .migpt.js  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置.env 文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;OPENAI_MODEL = Qwen/Qwen2.5-7B-Instruct  #这个免费 OPENAI_API_KEY= sk-xxxxxxxx   # 这里填写第三步中申请的API密钥即可 OPENAI_BASE_URL= https://api.siliconflow.cn/v1  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置.migpt.js文件&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;修改小米账号ID，密码，设备名称，在93行&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;// 小米 ID userId: &amp;quot;167857068&amp;quot;, // 注意：不是手机号或邮箱，请在「个人信息」-「小米 ID」查看 // 小米账号密码 password: &amp;quot;123456&amp;quot;, // 小爱音箱 DID 或在米家中设置的名称 did: &amp;quot;小爱音箱mini&amp;quot;, // 注意空格、大小写和错别字（音响 👉 音箱） &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;修改MIoT设备指令，在127行，具体值看开头表格&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;// TTS 指令，请到 https://home.miot-spec.com 查询具体指令 ttsCommand: [5, 1], // 设备唤醒指令，请到 https://home.miot-spec.com 查询具体指令 wakeUpCommand: [5, 2], // 查询是否在播放中指令，请到 https://home.miot-spec.com 查询具体指令 playingCommand: [4, 1, 1], // 默认无需配置此参数，查询播放状态异常时再尝试开启 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;修改人设，第4-16行，人物提示词，可以先跑通再改&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;// 小爱音箱扮演角色的简介 const botProfile = ` 性别：女 性格：乖巧可爱 爱好：喜欢搞怪，爱吃醋，爱撒娇，喜欢思考哲学问题，很爱干净，特别喜欢挑逗老公。 `.trim();  // 小爱音箱主人（你）的简介 const masterProfile = ` 性别：男 性格：善良正直 其他：是个程序员，总是舍己为人，是小丽丽的老公。 `.trim(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;回复唤醒指令，更改姓名 ,第104-119行，，让名字一致，可以先跑通再改&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;// 当消息以下面的关键词开头时，会调用 AI 来回复消息  callAIKeywords: [&amp;quot;请&amp;quot;, &amp;quot;你&amp;quot;, &amp;quot;小丽丽&amp;quot;],  // 当消息以下面的关键词开头时，会进入 AI 唤醒状态  wakeUpKeywords: [&amp;quot;打开&amp;quot;, &amp;quot;进入&amp;quot;, &amp;quot;召唤&amp;quot;],  // 当消息以下面的关键词开头时，会退出 AI 唤醒状态  exitKeywords: [&amp;quot;关闭&amp;quot;, &amp;quot;退出&amp;quot;, &amp;quot;再见&amp;quot;],  // 进入 AI 模式的欢迎语  onEnterAI: [&amp;quot;我是小丽丽，又见到老公啦，太开心了&amp;quot;], // 设为空数组时可关闭提示语  // 退出 AI 模式的提示语  onExitAI: [&amp;quot;小丽丽先回去咯&amp;quot;], // 为空时可关闭提示语  // AI 开始回答时的提示语  onAIAsking: [], // 为空时可关闭提示语  // AI 结束回答时的提示语  onAIReplied: [], // 为空时可关闭提示语  // AI 回答异常时的提示语  onAIError: [&amp;quot;啊哦，出错了，请稍后再试吧！&amp;quot;], // 为空时可关闭提示语 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;打开连续对话功能，第149-156行，是否支持请根据官方文档自行判断&lt;/p&gt; &lt;pre&gt;&lt;code&gt; // 是否启用连续对话功能，部分小爱音箱型号无法查询到正确的播放状态，需要关闭连续对话  streamResponse: true,  // 连续对话时，无响应多久后自动退出  exitKeepAliveAfter: 30, // 默认 30 秒，建议不要超过 1 分钟  // 连续对话时，下发 TTS 指令多长时间后开始检测设备播放状态（默认 3 秒）  checkTTSStatusAfter: 3, // 当小爱长文本回复被过早中断时，可尝试调大该值  // 连续对话时，播放状态检测间隔（单位毫秒，最低 500 毫秒，默认 1 秒）  checkInterval: 1000, // 调小此值可以降低小爱回复之间的停顿感，请酌情调节 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;这里一般只需要把连续对话功能改为true或false，如果设备支持可以改为true&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;安装依赖并运行&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;pnpm install pnpm build &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 23 Oct 2024 16:16:00 GMT</pubDate>
    </item>
    <item>
      <title>ESXi8.0 安装iKuai</title>
      <link>https://maruifu.cn/article/324</link>
      <content:encoded>&lt;h2&gt;网卡直通&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929164814222.png" alt="image-20240929164814222" title="image-20240929164814222" /&gt;&lt;/p&gt; &lt;p&gt;网卡出了第一个其他的都直通了,状态显示活动表示已经打开&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;第一个如果改了无法链接管理后台了&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;下载Ikuai&lt;/h2&gt; &lt;p&gt;&lt;a href="https://www.ikuai8.com/component/download" target="_blank"&gt;下载地址&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929162751838.png" alt="image-20240929162751838" title="image-20240929162751838" /&gt;&lt;/p&gt; &lt;h2&gt;创建虚拟机&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929162917139.png" alt="image-20240929162917139" title="image-20240929162917139" /&gt;&lt;/p&gt; &lt;h3&gt;选择名称和系统&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929163001220.png" alt="image-20240929163001220" title="image-20240929163001220" /&gt;&lt;/p&gt; &lt;h3&gt;选择储存&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929163032809.png" alt="image-20240929163032809" title="image-20240929163032809" /&gt;&lt;/p&gt; &lt;h3&gt;设置配置&lt;/h3&gt; &lt;h4&gt;虚拟硬件&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929163233330.png" alt="image-20240929163233330" title="image-20240929163233330" /&gt;&lt;/p&gt; &lt;h4&gt;CD/DVD驱动器&lt;/h4&gt; &lt;h4&gt;选择数据存储ISO文件-&amp;gt;选择镜像&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929163414427.png" alt="image-20240929163414427" title="image-20240929163414427" /&gt;&lt;/p&gt; &lt;h4&gt;虚拟机选项&lt;/h4&gt; &lt;p&gt;取消UEFI安全引导-下一页&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929163550822.png" alt="image-20240929163550822" title="image-20240929163550822" /&gt;&lt;/p&gt; &lt;h2&gt;安装IKuai&lt;/h2&gt; &lt;h3&gt;选择UEFI引导&lt;/h3&gt; &lt;p&gt;输入y&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929163844552.png" alt="image-20240929163844552" title="image-20240929163844552" /&gt;&lt;/p&gt; &lt;h3&gt;将系统安装到硬盘1 sda&lt;/h3&gt; &lt;p&gt;输入1  输入y&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929164138389.png" alt="image-20240929164138389" title="image-20240929164138389" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;安装后关闭电源&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;添加直通网卡设备&lt;/h3&gt; &lt;p&gt;虚拟机-&amp;gt;编辑-添加其他设备-PCI设备(有几个添加几个)&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我是6网口的,除了一个管理口,还有5个&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929165735978.png" alt="image-20240929165735978" title="image-20240929165735978" /&gt;&lt;/p&gt; &lt;p&gt;打开电源显示一下页面就说明好了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929170004122.png" alt="image-20240929170004122" title="image-20240929170004122" /&gt;&lt;/p&gt; &lt;h3&gt;设置IP地址&lt;/h3&gt; &lt;p&gt;输入2 然后输入0&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240929170248512.png" alt="image-20240929170248512" title="image-20240929170248512" /&gt;&lt;/p&gt; &lt;h2&gt;访问Ikuai&lt;/h2&gt; &lt;p&gt;访问刚才输入的IP地址,账号密码都是admin,admin&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/30/image-20240930111523054.png" alt="image-20240930111523054" title="image-20240930111523054" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 30 Sep 2024 03:17:04 GMT</pubDate>
    </item>
    <item>
      <title>8505 CPU安装ESXI</title>
      <link>https://maruifu.cn/article/323</link>
      <content:encoded>&lt;h2&gt;制作启动U盘&lt;/h2&gt; &lt;h3&gt;下载Ventoy&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.ventoy.net/cn/index.html" target="_blank"&gt;下载地址&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291050233181f796b393cdd027c.png" alt="image-20240929105023318" title="image-20240929105023318" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291051545717e5fd975439a91bd.png" alt="image-20240929105154571" title="image-20240929105154571" /&gt;&lt;/p&gt; &lt;h3&gt;制作启动U盘&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929111153695e5379c13e2096351.png" alt="image-20240929111153695" title="image-20240929111153695" /&gt;&lt;/p&gt; &lt;h3&gt;下载ESXI 8.0镜像&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;文件名&lt;/th&gt;&lt;th align="left"&gt;发行日期&lt;/th&gt;&lt;th align="left"&gt;备注&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;VMware-VMvisor-Installer-8.0U3b-24280767.x86_64.iso 【&lt;em&gt;MD5SUM:fa1fbb1b8c9830213d5afcee3e02000c&lt;/em&gt;】&lt;/td&gt;&lt;td align="left"&gt;2024-09-17&lt;/td&gt;&lt;td align="left"&gt;ESXi 8.0U3b&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4&gt;官网下载&lt;/h4&gt; &lt;p&gt;官方地址:https://www.broadcom.com/&lt;/p&gt; &lt;h4&gt;百度网盘下载&lt;/h4&gt; &lt;p&gt;&lt;a href="https://pan.baidu.com/s/1VHl-_uoUz9xyhxFYzGX1ug" target="_blank"&gt;下载镜像&lt;/a&gt; 提取码: &lt;code&gt;qm4n&lt;/code&gt;&lt;/p&gt; &lt;h4&gt;备用下载&lt;/h4&gt; &lt;p&gt;&lt;a href="https://pan.x86pi.cn/%E5%BC%80%E6%BA%90OS/1.%E8%99%9A%E6%8B%9F%E5%8C%96/2.Vmware%20vSphere%20ESXi" target="_blank"&gt;备用下载镜像1&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://nas.mrf.ink:5001/sharing/zFUMOgJ71" target="_blank"&gt;备用下载镜像2&lt;/a&gt;  密码&lt;code&gt;esxi&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;下载PE工具&lt;/h3&gt; &lt;p&gt;&lt;a href="https://pan.quark.cn/s/9ad80b099624" target="_blank"&gt;下载链接&lt;/a&gt;  提取码：&lt;code&gt;LxKK&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;复制到U盘&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;WePE64_V2.2.iso&lt;/li&gt; &lt;li&gt;VMware-VMvisor-Installer-8.0U3b-24280767.x86_64.iso&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;将以上文件复制到U盘中&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291217459329291bc2c48a6c627.png" alt="image-20240929121745932" title="image-20240929121745932" /&gt;&lt;/p&gt; &lt;h2&gt;安装ESXI&lt;/h2&gt; &lt;h3&gt;进入启动菜单&lt;/h3&gt; &lt;p&gt;在电脑上插入制作好的启动U盘，重启电脑时不停按 F11&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;大部分电脑都可以直接通过u盘启动快捷键来选择U盘启动，少部分电脑只能通过bios设置u盘为第一启动项。&lt;/p&gt; &lt;p&gt;有的是&lt;code&gt;F11&lt;/code&gt; ,&lt;code&gt;F12&lt;/code&gt;,&lt;code&gt;F7&lt;/code&gt;,&lt;code&gt;ESC&lt;/code&gt;等快捷键,不同的电脑也不一样&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;选择U盘启动&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929123014451-20240929153343294.png" alt="image-20240929123014451" title="image-20240929123014451" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;每个人的U盘型号不一样,选择自己的 我的是金士顿的.&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;选择上传的ESXI镜像&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291233384823620f2de307d669f.png" alt="image-20240929123338482" title="image-20240929123338482" /&gt;&lt;/p&gt; &lt;h3&gt;选择普通模式&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-2024092912342176137519743ac74c7e4.png" alt="image-20240929123421761" title="image-20240929123421761" /&gt;&lt;/p&gt; &lt;h3&gt;进入命令模式&lt;/h3&gt; &lt;p&gt;输入 &lt;code&gt;shift + O&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291235109426cad4a4ae131ed6b.png" alt="image-20240929123510942" title="image-20240929123510942" /&gt;&lt;/p&gt; &lt;h3&gt;输入跳过检查命令&lt;/h3&gt; &lt;p&gt;命令前加空格&lt;/p&gt; &lt;pre&gt;&lt;code&gt; cpuUniformityHardCheckPanic=FALSE &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929123742982133e75b2e1be9808.png" alt="image-20240929123742982" title="image-20240929123742982" /&gt;&lt;/p&gt; &lt;h3&gt;输入回车键继续&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929124419525536bdf11e7e558db.png" alt="image-20240929124419525" title="image-20240929124419525" /&gt;&lt;/p&gt; &lt;h3&gt;接受协议&lt;/h3&gt; &lt;p&gt;输入&lt;code&gt;F11&lt;/code&gt;,继续&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291258352769e93329fb7237f53.png" alt="image-20240929125835276" title="image-20240929125835276" /&gt;&lt;/p&gt; &lt;h3&gt;选择硬盘&lt;/h3&gt; &lt;p&gt;选择设备所在的硬盘,输入回车键,继续&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-2024092912591327899cc2e247f83d4f7.png" alt="image-20240929125913278" title="image-20240929125913278" /&gt;&lt;/p&gt; &lt;p&gt;回车继续&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291334301153af3511a094328d2.png" alt="image-20240929133430115" title="image-20240929133430115" /&gt;&lt;/p&gt; &lt;h3&gt;选择键盘布局&lt;/h3&gt; &lt;p&gt;默认选择,输入回车键,继续&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291335220425c1b071f8cba0107.png" alt="image-20240929133522042" title="image-20240929133522042" /&gt;&lt;/p&gt; &lt;h3&gt;输入密码&lt;/h3&gt; &lt;p&gt;输入密码后,回车键继续(上下键切换)&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929125012555c8ff34f9774e0faf.png" alt="image-20240929125012555" title="image-20240929125012555" /&gt;&lt;/p&gt; &lt;h3&gt;确认安装&lt;/h3&gt; &lt;p&gt;输入&lt;code&gt;F11&lt;/code&gt;,继续&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929133637211143f263f7d9be1b2.png" alt="image-20240929133637211" title="image-20240929133637211" /&gt;&lt;/p&gt; &lt;h3&gt;关机重启进入PE&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929133755627f14a244c04de82d8.png" alt="image-20240929133755627" title="image-20240929133755627" /&gt;&lt;/p&gt; &lt;h3&gt;进入启动菜单&lt;/h3&gt; &lt;p&gt;在电脑上插入制作好的启动U盘，重启电脑时不停按 F11&lt;/p&gt; &lt;h3&gt;选择U盘启动&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929123014451afba1733fe21fba5a636e8f893b17e0065dbccdf624019bf.png" alt="image-20240929123014451" title="image-20240929123014451" /&gt;&lt;/p&gt; &lt;h3&gt;选择上传的PE&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291339141659af99d121beda3d7.png" alt="image-20240929133914165" title="image-20240929133914165" /&gt;&lt;/p&gt; &lt;h3&gt;选择普通模式&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-2024092912342176137519743ac74c7e4.png" alt="image-20240929123421761" title="image-20240929123421761" /&gt;&lt;/p&gt; &lt;p&gt;将c盘和d盘的boot.cfg文件中第六行后加入 &lt;code&gt;cpuUniformityHardCheckPanic=FALSE&lt;/code&gt;，保存&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929134519459b3f8e7ee50f0a4fd.png" alt="image-20240929134519459" title="image-20240929134519459" /&gt;&lt;/p&gt; &lt;p&gt;进入C盘 选择 BOOT.CFG文件修改&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-202409291348197796d77be9f4ebfec8b.png" alt="image-20240929134819779" title="image-20240929134819779" /&gt;&lt;/p&gt; &lt;p&gt;进入D盘 BOOT.CFG文件修改&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929135034260385d616c80697a1b.png" alt="image-20240929135034260" title="image-20240929135034260" /&gt;&lt;/p&gt; &lt;h3&gt;重启&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929135550851bce65b77db0dd887.png" alt="image-20240929135550851" title="image-20240929135550851" /&gt;&lt;/p&gt; &lt;h3&gt;进入系统&lt;/h3&gt; &lt;p&gt;访问网址: https://192.168.2.3&lt;/p&gt; &lt;p&gt;输入账号: root&lt;/p&gt; &lt;p&gt;密码: 刚才设置的密码&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929140333442618da27b6aaae5f1.png" alt="image-20240929140333442" title="image-20240929140333442" /&gt;&lt;/p&gt; &lt;h2&gt;ESXi 其他问题&lt;/h2&gt; &lt;h3&gt;解决虚拟内存占用过大问题&lt;/h3&gt; &lt;p&gt;默认安装会分配一个 VMFSL(VMFS-L)(Local VMFS)很大空间(120G).&lt;/p&gt; &lt;p&gt;在安装界面出现时5秒内, 快速按 Shift+O&lt;/p&gt; &lt;p&gt;设置 &lt;code&gt;autoPartitionOSDataSize=8192&lt;/code&gt; 在&lt;code&gt;cdromBoot runweasel&lt;/code&gt; 之后输入&lt;code&gt;autoPartitionOSDataSize=8192&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cdromBoot runweasel autoPartitionOSDataSize=8192 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;检测到 TPM 2.0 设备，但无法建立连接。&lt;/h3&gt; &lt;p&gt;开机按DEL进入BIOS，进入 Advanced – Trusted Computing – Security Device Support，改为disable。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929154411266.png" alt="image-20240929154411266" title="image-20240929154411266" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/29/image-20240929154417421.png" alt="image-20240929154417421" title="image-20240929154417421" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 29 Sep 2024 06:04:00 GMT</pubDate>
    </item>
    <item>
      <title>github网站部署</title>
      <link>https://maruifu.cn/article/322</link>
      <content:encoded>&lt;p&gt;[TOC]&lt;/p&gt; &lt;h2&gt;手动部署&lt;/h2&gt; &lt;h3&gt;介绍&lt;/h3&gt; &lt;p&gt;&lt;code&gt;GitHub Pages&lt;/code&gt; 是一个静态站点托管服务。&lt;/p&gt; &lt;p&gt;如果你想记录生活、抒发情感、分享见解，这一切都离不开一个可以承载文字的平台，一个独立的、由自己掌控的平台，而 &lt;code&gt;GitHub Pages&lt;/code&gt; 就是这么一个平台。&lt;/p&gt; &lt;p&gt;在这个平台里你可以使用自己的个性域名；可以在海量的主题里挑选最适合你的那一款，如果你技术极客，也可以根据自己的喜好，设计属于自己的个性化页面；你既可以在线创建和发布网站，也可以在本地通过客户端工具或者命令行进行网站和内容的管理。&lt;/p&gt; &lt;p&gt;你完全可以通过 &lt;code&gt;GitHub Pages&lt;/code&gt; 展示和输出自身价值，甚至可以把它打造成属于自己的互联网「身份证」。&lt;/p&gt; &lt;p&gt;如果你想仔细了解 &lt;code&gt;GitHub Pages&lt;/code&gt;，&lt;a href="https://docs.github.com/cn/pages" target="_blank"&gt;点击这里&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;优势&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;GitHub Pages 完全免费，不需要个人购买服务器、域名&lt;/li&gt; &lt;li&gt;官方文档详细，不需要强大的编程能力，只需要一步一步按着操作来使用&lt;/li&gt; &lt;li&gt;支持的功能多，你可以绑定你的域名、使用免费的 HTTPS、自己 DIY 网站的主题、使用他人开发好的插件等等&lt;/li&gt; &lt;li&gt;完成搭建后，只需要专注于文章即可，其他诸如环境搭建、系统维护、文件存储的事情一概不用操心，都由 GitHub Pages 处理&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;......&lt;/p&gt; &lt;h3&gt;限制&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;GitHub Pages&lt;/code&gt; 源仓库建议限制为 1GB；&lt;/li&gt; &lt;li&gt;发布的 &lt;code&gt;GitHub Pages&lt;/code&gt; 网站可能不超过 1GB；&lt;/li&gt; &lt;li&gt;&lt;code&gt;GitHub Pages&lt;/code&gt; 网站每月的带宽限制为 100GB；&lt;/li&gt; &lt;li&gt;&lt;code&gt;GitHub Pages&lt;/code&gt; 网站每小时限制 10 个软件。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;开始使用&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;首先，你必须拥有一个 &lt;code&gt;GitHub&lt;/code&gt; 账号，&lt;a href="https://github.com/" target="_blank"&gt;注册点击这里&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;创建一个仓库用于存放自己的博客项目，如图：点击右上角的 + 号&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/image-20240920141352464.png" alt="image-20240920141352464" title="image-20240920141352464" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;填写自己的仓库信息&lt;/p&gt; &lt;p&gt;![image-20240920141952150](/Users/maruifu/Library/Application Support/typora-user-images/image-20240920141952150.png)&lt;/p&gt; &lt;p&gt;如我的网站仓库名就叫 &lt;code&gt;ac&lt;/code&gt;，因为我打算创建一个空调网站&lt;/p&gt; &lt;p&gt;::: details 防止图片失效，这里记录步骤和gitignore文件内容&lt;/p&gt; &lt;ol&gt; &lt;li&gt;填写仓库名&lt;/li&gt; &lt;li&gt;填写仓库描述，可以不写&lt;/li&gt; &lt;li&gt;选择公有 &lt;code&gt;Public&lt;/code&gt;&lt;/li&gt; &lt;li&gt;建议选择 &lt;code&gt;Add a README file&lt;/code&gt;，添加README文档&lt;/li&gt; &lt;li&gt;&lt;code&gt;Add .gitignore&lt;/code&gt; 是添加 &lt;code&gt;.gitignore&lt;/code&gt; 文件，如果不知道文件内容，可以不选。则需要手动在自己的代码目录下创建 &lt;code&gt;.gitignore&lt;/code&gt;文档，填入如下内容：&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-md"&gt;# env .env  *.log  yarn.lock package-lock.json # pnpm-lock.yaml .eslintcache # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.  # dependencies /node_modules /.pnp .pnp.js  # testing /coverage  # production /build  # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local  npm-debug.log* yarn-debug.log* yarn-error.log*  # jetbrains .idea  dist dist-ssr *.local &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;:::&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;上传代码&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;创建仓库后，进入仓库，我们上传代码&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/image-20240920142635266.png" alt="image-20240920142635266" title="image-20240920142635266" /&gt;&lt;/p&gt; &lt;p&gt;在项目所在目录下执行命令,使用SSH&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;GitHub 从2021年8月13日 09:00 PST 开始就不接受使用账户密码对 GitHub.com 进行 git 操作了。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;本地项目&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;git init git add . git commit -m &amp;quot;first commit&amp;quot; git branch -M main git remote add origin git@github.com:MaRuifu/ac.git git push -u origin main &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;配置github上传(可选)&lt;/h4&gt; &lt;p&gt;git使用SSH配置， 初始需要以下三个步骤&lt;/p&gt; &lt;ol&gt; &lt;li&gt;使用秘钥生成工具生成rsa秘钥和公钥&lt;/li&gt; &lt;li&gt;将rsa公钥添加到代码托管平台&lt;/li&gt; &lt;li&gt;将rsa秘钥添加到ssh-agent中，为ssh client指定使用的秘钥文件&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;第一步&lt;/strong&gt;：检查本地主机是否已经存在ssh key&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;cd ~/.ssh ls //看是否存在 id_rsa 和 id_rsa.pub文件，如果存在，说明已经有SSH Key &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;第二步&lt;/strong&gt;：生成ssh key&lt;/p&gt; &lt;p&gt;如果不存在ssh key，使用如下命令生成&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ssh-keygen -t rsa -C &amp;quot;xxx@xxx.com&amp;quot; //执行后一直回车即可 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;生成完以后再用第二步命令，查看ssh key&lt;/p&gt; &lt;p&gt;&lt;strong&gt;第三步&lt;/strong&gt;：获取ssh key公钥内容（id_rsa.pub）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd ~/.ssh cat id_rsa.pub  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;第四步&lt;/strong&gt;：Github账号上添加公钥&lt;/p&gt; &lt;p&gt;进入Settings设置&lt;/p&gt; &lt;p&gt;添加ssh key，把刚才复制的内容粘贴上去保存即可&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/image-20240920145449143.png" alt="image-20240920145449143" title="image-20240920145449143" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;第五步&lt;/strong&gt;：&lt;strong&gt;验证是否设置成功&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;ssh -T git@github.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;显示如下信息表明设置成功&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maruifu@maruifudeMacBook-Pro .ssh % ssh -T git@github.com Hi MaRuifu! You've successfully authenticated, but GitHub does not provide shell access. &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;上传超时(可选)&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;首先输入以下命令检查SSH是否能够连接成功（ssh后面有空格）&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;maruifu@maruifudeMacBook-Pro .ssh % ssh -T git@github.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;发现报错：端口连接超时。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ssh: connect to host github.com port 22: Connection timed out &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解决方案 在你的主机名文件夹中找到.ssh文件夹；（此前配置SSH时会生成该文件夹） 在.ssh文件夹中新建文件 config,不带后缀（可以新建文本文档，去掉.txt后缀） 使用文本软件打开config文件，输入以下内容，保存后即可 其中email@maruifu.cn 为你的邮箱&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Host github.com User email@maruifu.cn  Hostname ssh.github.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_rsa Port 443 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再次执行&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;ssh -T git@github.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;会出现以下提示，输入yes回车即可&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maruifu@maruifudeMacBook-Pro .ssh % ssh -T git@github.com The authenticity of host '[ssh.github.com]:443 ([20.205.243.160]:443)' can't be established. ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再次执行ssh -T git@github.com，发现验证通过–接下来可以正常上传代码&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;Hi Clare! You've successfully authenticated, but GitHub does not provide shell access. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;上传打包后的文件&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;创建仓库后，进入仓库，然后创建一个分支，名字必须叫做&lt;code&gt;gh-pages&lt;/code&gt;，可以理解为默认只识别这个名字&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;创建好的仓库默认主支是 &lt;code&gt;main&lt;/code&gt;，现在 GitHub 逐渐让 &lt;code&gt;main&lt;/code&gt; 作为默认的主支名&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2025/11/20/image-20240923085015405.png" alt="image-20240923085015405" title="image-20240923085015405" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;::: details 防止图片失效，这里记录步骤  1. 点击 `main`，弹出拉下框 2. 搜索 `gh-pages`，必须是这个 3. 创建 `gh-pages`。搜索后，会显示 `Create branch：gh-pages from main`，点击即可创建  ::: &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;为什么创建 &lt;code&gt;gh-pages&lt;/code&gt; 分支呢？因为 &lt;code&gt;master&lt;/code&gt; 主支放的是博客源码，而 &lt;code&gt;gh-pages&lt;/code&gt; 分支放的是主支&lt;mark&gt;打包&lt;/mark&gt;后的博客代码。&lt;/p&gt; &lt;p&gt;不理解打包，可以这么认为：写的 &lt;code&gt;markdown&lt;/code&gt; 文档是源码，要想显示到网页上，需要变成 &lt;code&gt;html&lt;/code&gt; 文件，&lt;mark&gt;打包&lt;/mark&gt;就是变成 &lt;code&gt;html&lt;/code&gt; 的过程。&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;上传打包项目到 &lt;code&gt;gh-pages&lt;/code&gt; 分支，我的是在 项目下的 &lt;code&gt;dist&lt;/code&gt; 目录下&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;git init  # 初始化 git add .  # 把项目的所有文件添加到本地仓库 git commit -m &amp;quot;first commit&amp;quot;  # 双引号里是对于添加文件的描述 git push -f URL main:gh-pages # URL就是你的仓库地址,推送到github gh-pages分支 ,我这里是 git@github.com:MaRuifu/ac.git &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;-f 是必须的，意味着覆盖原来的内容&lt;/p&gt; &lt;p&gt;如果可以在仓库 &lt;code&gt;main&lt;/code&gt; 下看到源码，&lt;code&gt;gh-pages&lt;/code&gt; 下看到打包后的文件，代表成功了&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;开启部署&lt;/h2&gt; &lt;p&gt;:::note&lt;/p&gt; &lt;p&gt;这一内容为开启 &lt;code&gt;Github Page&lt;/code&gt;是为了连接手动部署成功后的操作，如果手动部署失败或者想直接使用脚本部署，则至少二选一成功了再来进行本内容的操作。&lt;/p&gt; &lt;p&gt;自动化部署前需要先开启 &lt;code&gt;Github Page&lt;/code&gt;&lt;/p&gt; &lt;p&gt;当部署成功时，没有看到想要的成果，我相信不会有人继续进行枯燥的操作。下面将开启 &lt;code&gt;Github Page&lt;/code&gt; 见证自己的部署成功页面。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;GitHub Page&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;![image-20240923105347519](/Users/maruifu/Library/Application Support/typora-user-images/image-20240923105347519.png)&lt;/p&gt; &lt;p&gt;::: details 防止图片失效，这里记录步骤&lt;/p&gt; &lt;ol&gt; &lt;li&gt;进入仓库，点击 &lt;code&gt;Settings&lt;/code&gt;&lt;/li&gt; &lt;li&gt;找到 &lt;code&gt;Pages&lt;/code&gt; 选项&lt;/li&gt; &lt;li&gt;选择要部署的分支，一定是 &lt;code&gt;gh-pages&lt;/code&gt; 分支&lt;/li&gt; &lt;li&gt;蓝色框的地址，就是所有人都能访问的地址，访问之后，就能看到你的博客&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;:::&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;code&gt;Github&lt;/code&gt; 一旦更新 &lt;code&gt;gh-pages&lt;/code&gt; 分支内容，会自动重新部署，所以访问页面是最新的。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;脚本部署&lt;/h2&gt; &lt;p&gt;::: note 注意&lt;/p&gt; &lt;p&gt;脚本部署仅适用于把打包后的文件推送到 &lt;code&gt;GitHub&lt;/code&gt; 仓库的 &lt;code&gt;gh-pages&lt;/code&gt; 分支下，不适用把项目源码推送到 &lt;code&gt;main&lt;/code&gt; 主支上。&lt;/p&gt; &lt;p&gt;所以还得手动执行命令把源码推送到 GitHub 仓库。当然&lt;a href="#自动化部署" target="_blank"&gt;自动化部署&lt;/a&gt;，可以全部解决。&lt;/p&gt; &lt;p&gt;:::&lt;/p&gt; &lt;p&gt;每次写完文档，如果都需要像上面&lt;a href="#上传代码" target="_blank"&gt;上传代码&lt;/a&gt;中手动打包，无疑是繁琐且耗费时间的，学会使用脚本解决频繁的问题，能更加专注于文章。&lt;/p&gt; &lt;p&gt;&lt;code&gt;shell&lt;/code&gt; 脚本，请自行学习，日后我更新了 &lt;code&gt;shell&lt;/code&gt; 的学习文档，这里会提供跳转地址 「*记录」。&lt;/p&gt; &lt;p&gt;该脚本是推送项目到 &lt;code&gt;GitHub&lt;/code&gt;，如果推送到 &lt;code&gt;Gitee&lt;/code&gt;，修改仓库地址即可，脚本名叫 &lt;code&gt;deploy.sh&lt;/code&gt;，请放在项目的根目录下。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;#!/usr/bin/env sh  # 确保脚本抛出遇到的错误 set -e  # 生成静态文件 npm run build  # 进入生成的文件夹 cd dist  # 如果手运行该脚本，则执行if里的，如果是GitHub自动执行该脚本，则是else里的 if [ -z &amp;quot;$GITHUB_TOKEN&amp;quot; ]; then  msg='deploy'  githubUrl=git@github.com:maruifu/ac.git  # 替换自己的 GitHub 仓库地址，SSH格式 else  msg='来自github actions的自动部署'  # 替换自己的 GitHub 仓库地址，更改的是 @后面的地址 以及 把Kele-Bingtang 改为自己用户名  githubUrl=https://maruifu:${GITHUB_TOKEN}@github.com/maruifu/ac.git   git config --global user.name &amp;quot;maruifu&amp;quot;   # 修改为自己的 GitHub 用户名  git config --global user.email &amp;quot;mrf_it@163.com&amp;quot;  # 修改为自己的 GitHub 邮箱，注册时绑定的邮箱 fi git init git add -A git commit -m &amp;quot;${msg}&amp;quot; git push -f $githubUrl main:gh-pages # 推送到github gh-pages分支  cd - # 退回开始所在目录 rm -rf dist &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在项目根目录打开 &lt;code&gt;bash&lt;/code&gt; 命令窗口或者 &lt;code&gt;Git Bash Here&lt;/code&gt; 窗口，执行该脚本&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;sh deploy.sh  # 假设你的脚本叫 deploy.sh &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;自动化部署&lt;/h2&gt; &lt;p&gt;使用了脚本部署，相比较手动部署，少了打包的过程，节省了时间，但是还要手动执行两次过程：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;敲打命令推送项目源码到仓库的 &lt;code&gt;main&lt;/code&gt; 上&lt;/li&gt; &lt;li&gt;执行 &lt;code&gt;shell&lt;/code&gt; 脚本，自动打包并推送打包内容到仓库的 &lt;code&gt;gh-pages&lt;/code&gt; 上&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;自动化部署利用了 &lt;code&gt;GitHub Actions&lt;/code&gt; ，仅需实现过程1：推送项目源码到 &lt;code&gt;master&lt;/code&gt;，即可自动实现过程 2&lt;/p&gt; &lt;p&gt;过程 1 可以写一个脚本执行。这样执行一个脚本，即可实现自动化，下方有该脚本，名叫 &lt;code&gt;push.sh&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;介绍&lt;/h3&gt; &lt;p&gt;&lt;a href="https://github.com/features/actions" target="_blank"&gt;GitHub Actions&lt;/a&gt; 是 GitHub 的持续集成服务，于 2018 年 10 月推出。&lt;/p&gt; &lt;p&gt;&lt;a href="http://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html?20191227113947#comment-last" target="_blank"&gt;GitHub Actions 入门教程&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://github.com/actions" target="_blank"&gt;GitHub Actions 商城&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;生成token&lt;/h3&gt; &lt;p&gt;首先，获取 token，这是 GitHub 的另一种授权方式，即不需要用户名和密码&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;进入 GitHub，点击头像，找到 setting&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101184344.png" alt="image-20211101184343641" title="image-20211101184343641" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;找到 Developer settings 并点击&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101184415.png" alt="image-20211101184414278" title="image-20211101184414278" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;点击 &lt;code&gt;Persional access tokens&lt;/code&gt;，然后点击 &lt;code&gt;Generate new token&lt;/code&gt; 生成toke&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101184621.png" alt="image-20211101184550152" title="image-20211101184550152" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;填写 &lt;code&gt;Note&lt;/code&gt;，即 &lt;code&gt;token&lt;/code&gt; 的描述，并选择 &lt;code&gt;repo&lt;/code&gt;，其他不用选&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101184727.png" alt="image-20211101184725720" title="image-20211101184725720" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;如果不想你的 &lt;code&gt;token&lt;/code&gt; 过期，在 &lt;code&gt;Expiration&lt;/code&gt; 选中 &lt;code&gt;No expiration&lt;/code&gt; 即可&lt;/p&gt; &lt;p&gt;&lt;code&gt;token&lt;/code&gt; 生成后，记得保存下来，因为页面一旦关闭，&lt;code&gt;token&lt;/code&gt; 不会在显示&lt;/p&gt; &lt;h3&gt;配置密钥&lt;/h3&gt; &lt;p&gt;拿到了 token，需要将该 token 进行配置，赋予某仓库权限&lt;/p&gt; &lt;p&gt;内容分为三步，其中第三步看需求，第一、二步则是必须。&lt;/p&gt; &lt;p&gt;第一步：配置 &lt;code&gt;ACCESS_TOKEN&lt;/code&gt;；&lt;/p&gt; &lt;p&gt;第二步：配置 &lt;code&gt;SSH key&lt;/code&gt;&lt;/p&gt; &lt;p&gt;第三步：配置 &lt;code&gt;GITEE_RSA_PRIVATE_KEY&lt;/code&gt;和 &lt;code&gt;GITEE_PASSWORD&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置 ACCESS_TOKEN&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;配置密钥是因为自动化部署过程，需要 GitHub 权限，没有 token，就没有权限部署&lt;/p&gt; &lt;p&gt;&lt;code&gt;ACCESS_TOKEN&lt;/code&gt; 是固定的，名字要和下方脚本使用的名对应上。如果需要改，双方都要改&lt;/p&gt; &lt;ul&gt; &lt;li&gt;找到仓库的 &lt;code&gt;Settings&lt;/code&gt; ，并点击 &lt;code&gt;Secrets&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101185216.png" alt="image-20211101185215129" title="image-20211101185215129" /&gt;&lt;/p&gt; &lt;p&gt;点击 &lt;code&gt;New repository secret&lt;/code&gt; 并进行配置，其中 Name 是 &lt;code&gt;ACCESS_TOKEN&lt;/code&gt;，Value 是之前生成的 &lt;code&gt;token&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101185457.png" alt="image-20211101185455984" title="image-20211101185455984" /&gt;&lt;/p&gt; &lt;p&gt;点击 &lt;code&gt;Add secret&lt;/code&gt; 即可该密钥添加成功&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置 SSH Key&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;打开 Git Bash 查看电脑上是否已经存在 SSH 密钥。如果有，请直接看第三步&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;cd ~/.ssh &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;或者前往&lt;code&gt;C:\Users\你的用户名&lt;/code&gt;下找 &lt;code&gt;.ssh&lt;/code&gt; 文件夹&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果没有则需要创建新的 &lt;code&gt;ssh key&lt;/code&gt;，打开 &lt;code&gt;Git Bash Here&lt;/code&gt;，输入：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;ssh-keygen -t rsa -C &amp;quot;你的绑定GitHub邮箱&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;1s 左右执行会停止，提示你选中文件保存路径，直接按 &lt;code&gt;Enter&lt;/code&gt; 即可，即路径就在&lt;code&gt;C:\Users\你的用户名&lt;/code&gt;下&lt;/p&gt; &lt;p&gt;1s 左右执行又会停止，提示你输入密码，这里不用输入，直接按 &lt;code&gt;Enter&lt;/code&gt; 即可&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在&lt;code&gt;C:\Users\你的用户名&lt;/code&gt;目录下生成 &lt;code&gt;.ssh&lt;/code&gt; 文件夹，去文件夹里找到两个文件：&lt;code&gt;id_rsa&lt;/code&gt; 和 &lt;code&gt;id_rsa.pub&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;id_rsa.pub&lt;/code&gt; 为公钥，可以多地方使用&lt;/li&gt; &lt;li&gt;&lt;code&gt;id_rsa&lt;/code&gt; 为私钥，提供私钥能找到所有的公钥&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;复制 &lt;code&gt;id_rsa.pub&lt;/code&gt; 的内容，进入 &lt;code&gt;Github&lt;/code&gt; 的 &lt;code&gt;Settings&lt;/code&gt; 设置里，找到 &lt;code&gt;SSH and GPG keys&lt;/code&gt;，然后点击 &lt;code&gt;New SSH Key&lt;/code&gt; 进行配置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101204325.png" alt="image-20211101204250487" title="image-20211101204250487" /&gt;&lt;/p&gt; &lt;p&gt;点击 &lt;code&gt;New SSH Key&lt;/code&gt; 并进行配置：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101204644.png" alt="image-20211101204609335" title="image-20211101204609335" /&gt;&lt;/p&gt; &lt;p&gt;Title 就是 SSh Key 的标题；Key 填入 &lt;code&gt;id_rsa.pub&lt;/code&gt; 的内容，如果 Key 不对，说明打开该文件的编码格式有问题&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;测试 SSH 是否连接到 &lt;code&gt;GitHub&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;ssh -T git@github.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;成功连接标志：&lt;code&gt;You've sucessfully ...,but Github ... access&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;&lt;mark&gt;可选&lt;/mark&gt;步骤：配置 GITEE_RSA_PRIVATE_KEY 和 GITEE_PASSWORD&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;::: tip&lt;/p&gt; &lt;p&gt;如果需要 &lt;code&gt;GitHub&lt;/code&gt; 的仓库同步到 &lt;code&gt;Gitee&lt;/code&gt; 上，则执行这步骤。&lt;/p&gt; &lt;p&gt;下方的自动化文件里，有 &lt;code&gt;GitHub&lt;/code&gt; 仓库同步到 &lt;code&gt;Gitee&lt;/code&gt; 的实现代码，如果不需要，请注释或者删除。&lt;/p&gt; &lt;h3&gt;自动化文件&lt;/h3&gt; &lt;p&gt;&lt;code&gt;GitHub Actions&lt;/code&gt; 的实现需要一个 &lt;code&gt;yml&lt;/code&gt; 文件，在根目录下创建两个文件夹和一个文件：&lt;code&gt;/.github/workflows/ci.yml&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20211101190308.png" alt="image-20211101190242407" title="image-20211101190242407" /&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;myNote&lt;/code&gt; 是我的博客根目录。&lt;code&gt;ci.yml&lt;/code&gt; 的 &lt;code&gt;ci&lt;/code&gt; 可自定义&lt;/p&gt; &lt;p&gt;&lt;code&gt;ci.yml&lt;/code&gt; 文件内容以及过程：（我已经写好了注释）&lt;/p&gt; &lt;pre&gt;&lt;code class="language-yml"&gt;name: CI  #on: [push]  # 在 master 分支发生 push 事件时触发。 on:    push:     branches:       - master        env: # 设置环境变量   TZ: Asia/Shanghai # 时区（设置时区可使页面中的 最近更新时间 使用时区时间）  jobs: # 工作流   build: # 自定义名称     runs-on: ubuntu-latest # 必填，运行在虚拟机环境 ubuntu-latest      strategy:       matrix:         node-version: [14.x]      steps: # 步骤1       - name: Checkout # 步骤1，拉取代码       # 使用的动作。格式：userName/repoName。作用：检出仓库，获取源码。 官方actions库：https://github.com/actions         uses: actions/checkout@v1               - name: Use Node.js ${{ matrix.node-version }} # 步骤2         uses: actions/setup-node@v1 # 作用：安装nodejs         with:           node-version: ${{ matrix.node-version }} # 版本        - name: run deploy.sh # 步骤3：执行脚本 deploy.sh         env: # 设置环境变量，未设置则不运行           GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # token            SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} # 私钥         run: npm install &amp;amp;&amp;amp; npm run deploy  # 执行 deploy.sh 脚本，位于项目根目录        - name: Push Matser To Gitee  # 步骤4：GitHub 内容同步到 Gitee，同步仓库是 master         uses: wearerequired/git-mirror-action@master         env:           SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}         with:  # 从源到目的地           source-repo: 'git@github.com:Kele-Bingtang/notes-blog.git'    #Github 仓库地址           destination-repo: 'git@gitee.com:kele-bingtang/notes-blog.git'    #Gitee 仓库地址        - name: Build Gitee Pages # 步骤5：自动部署到 Gitee Pages         uses: yanglbme/gitee-pages-action@master         with:           # 注意替换为你的 Gitee 用户名           gitee-username: Kele-Bingtang           # 注意在 Settings -&amp;gt; Secrets 配置 GITEE_PASSWORD           gitee-password: ${{ secrets.GITEE_PASSWORD }}           # 注意替换为你的 Gitee 仓库，仓库名严格区分大小写，请准确填写，否则会出错           gitee-repo: Kele-Bingtang/notes-blog           # 要部署的分支，默认是 master，若是其他分支，则需要指定（指定的分支必须存在）           branch: gh-pages &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;::: details 点击打开，一些参数解释&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;name&lt;/code&gt; 是 workflow 的名称。如果省略该字段，默认为当前 workflow 的文件名，可自定义，查看步骤时显示的代号，叫 aa 都可以，描述步骤功能即可&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;on&lt;/code&gt; 指定触发 workflow 的条件，通常是某些事件，也可以是事件的数组。如上方就是 &lt;code&gt;push&lt;/code&gt; 到 &lt;code&gt;master&lt;/code&gt; 后触发&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;jobs&lt;/code&gt; 里面，需要写出每一项任务的 &lt;code&gt;job_id&lt;/code&gt;，具体名称自定义。&lt;code&gt;job_id&lt;/code&gt;里面的 &lt;code&gt;name&lt;/code&gt; 字段是任务的说明&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;runs-on&lt;/code&gt; 指定运行所需要的虚拟机环境。它是&lt;mark&gt;必填&lt;/mark&gt;字段&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;steps&lt;/code&gt; 指定每个 Job 的运行步骤，可以包含一个或多个步骤。每个步骤都可以指定三个字段：name、run、env&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;uses&lt;/code&gt; 用的就是别人写好的插件，持续集成由很多操作组成，GitHub 因此允许其他人把写好的插件共享到插件市场，以便他人使用。因此如果你需要某个 action，不必自己写复杂的脚本，直接引用他人写好的 action 即可。、。官网自带的格式：action/功能名@xx，别人写好的 &lt;code&gt;users&lt;/code&gt; 格式为：作者名/功能名@xx。作者名别改成自己的名字，我犯过这个错误，无需纠结为什么叫这个，我之前纠结过，人家规定的不可变的名字。类似于一个写好的命令脚本&lt;/p&gt; &lt;p&gt;要想找多个其他 &lt;code&gt;users&lt;/code&gt; 脚本，前往&lt;a href="https://github.com/actions" target="_blank"&gt;GitHub Actions 商城&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;env&lt;/code&gt; 指环境变量，也就是运行时需要的一些参数，如密钥，仓库地址等&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;with&lt;/code&gt; 表示传给插件的参数&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;:::&lt;/p&gt; &lt;p&gt;内容的 &lt;code&gt;steps&lt;/code&gt; 里的步骤详细如下：（一个 &lt;code&gt;name&lt;/code&gt; 代表一步）&lt;/p&gt; &lt;ol&gt; &lt;li&gt;拉取代码。不需要修改&lt;/li&gt; &lt;li&gt;检查 &lt;code&gt;Node.js&lt;/code&gt; 版本，安装指定的 &lt;code&gt;Nodejs&lt;/code&gt; 版本，如果版本对了，则会跳过这一步。不需要修改&lt;/li&gt; &lt;li&gt;执行脚本 &lt;code&gt;deploy.sh&lt;/code&gt;。也就是&lt;a href="#脚本部署" target="_blank"&gt;脚本部署&lt;/a&gt;的脚本，这个脚本会打包项目到 &lt;code&gt;gh-pages&lt;/code&gt; 目录下。需要&lt;a href="#配置密钥" target="_blank"&gt;配置密钥&lt;/a&gt;，&lt;code&gt;env&lt;/code&gt; 读取的就是密钥。上方说过密钥为什么固定是&lt;code&gt;ACCESS_TOKEN&lt;/code&gt;这些，因为这里读取的就是这些名字，如需修改，双方都要改&lt;/li&gt; &lt;li&gt;&lt;code&gt;GitHub&lt;/code&gt; 仓库同步到 &lt;code&gt;Gitee&lt;/code&gt;，同步仓库是 &lt;code&gt;master&lt;/code&gt;，如果没有这个打算，则注释或者删除掉，包括第五步&lt;/li&gt; &lt;li&gt;&lt;code&gt;Gitee&lt;/code&gt; 获得最新的代码后，不会重新部署新的代码。因为免费的 &lt;code&gt;Gitee Page&lt;/code&gt; 需要手动点击更新，而 &lt;code&gt;Github Page&lt;/code&gt; 自动😸。所以需要别人写好的 &lt;code&gt;users: yanglbme/gitee-pages-action@master&lt;/code&gt; 帮助点击更新&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;文件内容需要改的是仓库地址，以及 &lt;code&gt;Gitee&lt;/code&gt; 用户名&lt;/p&gt; &lt;p&gt;编写提交项目到 &lt;code&gt;master&lt;/code&gt; 仓库的脚本，叫 &lt;code&gt;push.sh&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;#!/usr/bin/env sh  # 本脚本为自动部署的入口脚本，只需执行该脚本，即可实现GitHub的自动部署，以及 push 到 GitHub 的所有文件同步到 Gitee 中  GITHUB_REPO=git@github.com:Kele-Bingtang/notes-blog.git  comment=$1  if [ ! $comment ]; then comment=&amp;quot;更新内容+action自动部署&amp;quot; fi  git add -A  git commit -m &amp;quot;${comment}&amp;quot;    # $1 是启动该脚本传来的参数，如 sh push.sh hello，其中 hello 就会插入到 $1 处，如果想两个参数，则加 $2  git push $GITHUB_REPO     &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在根目录下用 &lt;code&gt;Git Bash Here&lt;/code&gt; 打开，并执行该脚本&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sh"&gt;sh push.sh &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;一旦该脚本执行成功后，静待五六分钟，会发现 &lt;code&gt;master&lt;/code&gt; 主支更新了项目源码，&lt;code&gt;gh-pages&lt;/code&gt; 分支更新的项目打包后的代码。&lt;/p&gt; &lt;p&gt;&lt;code&gt;Gitee&lt;/code&gt; 对应的仓库 &lt;code&gt;master&lt;/code&gt; 主支也更新了项目源码，&lt;code&gt;gh-pages&lt;/code&gt; 分支也更新的项目打包后的代码。&lt;/p&gt; &lt;h3&gt;自动化文件流程&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;yml 被执行流程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当第一次提交项目到仓库时，&lt;code&gt;Github&lt;/code&gt; 会自动在项目根目录找到 &lt;code&gt;/.github/workflows/&lt;/code&gt; 下的 &lt;code&gt;yml&lt;/code&gt; 文件，如 &lt;code&gt;ci.yml&lt;/code&gt;，并且执行该 &lt;code&gt;ci.yml&lt;/code&gt; 文件；&lt;/p&gt; &lt;p&gt;当第二次乃至后面提交项目到仓库时，&lt;code&gt;Github&lt;/code&gt; 判断 &lt;code&gt;ci.yml&lt;/code&gt; 内容是否发送变化，没有则执行仓库的 &lt;code&gt;ci.yml&lt;/code&gt; 文件，有则先更新文件再执行。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;yml 执行流程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当把项目源码推送到仓库的 &lt;code&gt;master&lt;/code&gt; 主支时，该 &lt;code&gt;yml&lt;/code&gt; 文件开始执行。该文件执行的第三步，就会执行 &lt;code&gt;deploy.sh&lt;/code&gt; 脚本部署文件，完成把打包文件提交到 &lt;code&gt;gh-pages&lt;/code&gt; 分支下，该文件的第四步，则是把 &lt;code&gt;Github&lt;/code&gt; 仓库的代码同步到 &lt;code&gt;Gitee&lt;/code&gt; 仓库里，包括 &lt;code&gt;gh-pages&lt;/code&gt;分支（双方仓库名字要一致），该文件第五步，则是手动点击了 &lt;code&gt;Gitee&lt;/code&gt; 的更新部署按钮。（&lt;code&gt;Gitee&lt;/code&gt; 自动更新部署需要 99元/年，没钱只能利用脚本手动点击按钮更新）&lt;/p&gt; &lt;h2&gt;问答&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;Q1 - 能概括一下本内容吗？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;可以，概括的过程包括 &lt;code&gt;GitHub&lt;/code&gt; 和 &lt;code&gt;Gitee&lt;/code&gt;，如果只选择一方，选择性另一方忽略即可。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;code&gt;Github&lt;/code&gt; 和 &lt;code&gt;Gitee&lt;/code&gt; 分别创建一个仓库，并且分别创建 &lt;code&gt;gh-pages&lt;/code&gt; 分支，仓库名要一致&lt;/li&gt; &lt;li&gt;将写好的项目源码提交到 &lt;code&gt;master&lt;/code&gt; 主支，打包后的项目文件放到 &lt;code&gt;gh-pages&lt;/code&gt; 分支，主支名即 default&lt;/li&gt; &lt;li&gt;开启 &lt;code&gt;Github Pages&lt;/code&gt; 和 &lt;code&gt;Gitee Pages&lt;/code&gt;，两者都有自己的部署地址，前者网速慢，后者网速快&lt;/li&gt; &lt;li&gt;实现脚本部署，节省步骤 2 的打包项目提交时间，但是无法节省项目源码提交时间&lt;/li&gt; &lt;li&gt;首先生成 &lt;code&gt;token&lt;/code&gt;，然后生成公钥私钥，公钥分别配置在 &lt;code&gt;Github&lt;/code&gt; 和 &lt;code&gt;Gitee&lt;/code&gt; 网站上，&lt;code&gt;token&lt;/code&gt;、私钥、&lt;code&gt;Gitee&lt;/code&gt; 密码都配置在项目仓库上&lt;/li&gt; &lt;li&gt;配置 &lt;code&gt;yml&lt;/code&gt; 文件，在 &lt;code&gt;根目录/.github/workflows/&lt;/code&gt; 下创建 &lt;code&gt;ci.yml&lt;/code&gt; 文件，填写好内容&lt;/li&gt; &lt;li&gt;编写 &lt;code&gt;push.sh&lt;/code&gt; 脚本，填写好内容，每次写完文档后执行该脚本，即可实现完成自动化部署&lt;/li&gt; &lt;/ol&gt; &lt;hr /&gt; &lt;p&gt;&lt;strong&gt;Q2 - 实现自动化部署有多少个脚本？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;3个&lt;/p&gt; &lt;p&gt;&lt;code&gt;push.sh&lt;/code&gt;：提交源码到 &lt;code&gt;master&lt;/code&gt; 主支；&lt;/p&gt; &lt;p&gt;&lt;code&gt;deploy&lt;/code&gt;：提交打包文件到 &lt;code&gt;gh-pages&lt;/code&gt; 分支；&lt;/p&gt; &lt;h2&gt;问题&lt;/h2&gt; &lt;p&gt;记录我在部署的过程中遇到的问题，这些问题卡了我挺长时间：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;生成 token 和 Secrets 设置&lt;/li&gt; &lt;li&gt;配置 .git/conig 内容以及指令一样&lt;/li&gt; &lt;li&gt;.github/**.yml 设计&lt;/li&gt; &lt;li&gt;GitHub 自动部署，而 Gitee 需要手动&lt;/li&gt; &lt;li&gt;shell 脚本设计&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;更新base&lt;/h2&gt; &lt;p&gt;因为一些原因，我把 base 由仓库名改为了 &lt;code&gt;/&lt;/code&gt;，从而导致部署后访问的地址带有仓库名失效，正确做法：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;GitHub 仓库修改仓库名为：&lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;，这样直接访问 &lt;code&gt;https://&amp;lt;username&amp;gt;.github.io&lt;/code&gt; 就会生效&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20220104164505.png" alt="image-20220104164002661" title="image-20220104164002661" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Github 仓库修改路径，仓库名不重要，可改可不改，改为 &lt;code&gt;&amp;lt;username&amp;gt;.gitee.io&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20220104164507.png" alt="image-20220104164129335" title="image-20220104164129335" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;ci.yml 文件把原来的仓库地址修改为更新后的地址：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-yml"&gt;name: CI  #on: [push]  # 在 master 分支发生 push 事件时触发。 on:    push:     branches:       - master  jobs: # 工作流   build: # 自定义名称     runs-on: ubuntu-latest # 必填，运行在虚拟机环境 ubuntu-latest      strategy:       matrix:         node-version: [14.x]      steps:        - name: Checkout # 步骤1，拉取代码       # 使用的动作。格式：userName/repoName。作用：检出仓库，获取源码。 官方actions库：https://github.com/actions         uses: actions/checkout@v1               - name: Use Node.js ${{ matrix.node-version }} # 步骤2         uses: actions/setup-node@v1 # 作用：安装nodejs         with:           node-version: ${{ matrix.node-version }} # 版本        - name: run deploy.sh # 步骤3：执行脚本 deploy.sh         env: # 设置环境变量，未设置则不运行           GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # token            SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} # 私钥         run: npm install &amp;amp;&amp;amp; npm run deploy  # 执行 deploy.sh 脚本，位于项目根目录        - name: Push Matser To Gitee  # 步骤4：GitHub 内容同步到 Gitee，同步仓库是 master         uses: wearerequired/git-mirror-action@master         env:           SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}         with:  # 从源到目的地           source-repo: 'git@github.com:Kele-Bingtang/Kele-Bingtang.github.io.git'    #Github 仓库地址           destination-repo: 'git@gitee.com:kele-bingtang/Kele-Bingtang.gitee.io.git'    #Gitee 仓库地址        - name: Build Gitee Pages # 步骤5：自动部署到 Gitee Pages         uses: yanglbme/gitee-pages-action@master         with:           # 注意替换为你的 Gitee 用户名           gitee-username: Kele-Bingtang           # 注意在 Settings -&amp;gt; Secrets 配置 GITEE_PASSWORD           gitee-password: ${{ secrets.GITEE_PASSWORD }}           # 注意替换为你的 Gitee 仓库地址，仓库名严格区分大小写，一般地址和仓库名一致，如果地址改了，仓库名不改，要以地址为准（地址仅需后缀，即 https://gitee.com/&amp;lt;username&amp;gt;/xxx 的 xxx 即可）           gitee-repo: Kele-Bingtang/Kele-Bingtang.gitee.io           # 要部署的分支，默认是 master，若是其他分支，则需要指定（指定的分支必须存在）           branch: gh-pages &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;值得注意的是 51 行的 &lt;code&gt;gitee-repo&lt;/code&gt; 不是 Gitee 的仓库名字，而是修改的地址名：&lt;code&gt;&amp;lt;username&amp;gt;.gitee.io&lt;/code&gt;。&lt;/p&gt; &lt;h2&gt;自定义域名&lt;/h2&gt; &lt;p&gt;这里以 GitHub 举例，Gitee 同理。&lt;/p&gt; &lt;p&gt;前往你的域名解析处，进入如下配置：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20220104164500.png" alt="image-20220104164459334" title="image-20220104164459334" /&gt;&lt;/p&gt; &lt;p&gt;然后进入 GitHub 绑定自定义的域名&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/20/20220104164841.png" alt="image-20220104164839571" title="image-20220104164839571" /&gt;&lt;/p&gt; &lt;p&gt;::: warning&lt;/p&gt; &lt;p&gt;Gitee 的仓库不要改为 &lt;code&gt;&amp;lt;username&amp;gt;.gitee.io&lt;/code&gt;，而是直接改为 &lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; 即可。&lt;/p&gt; &lt;p&gt;:::&lt;/p&gt; &lt;p&gt;套用 Gitee 官方的一句话：&lt;/p&gt; &lt;p&gt;如果你想你的 pages 首页访问地址不带二级目录，如ipvb.gitee.io，&lt;strong&gt;你需要建立一个与自己个性地址同名的仓库&lt;/strong&gt;，如 https://gitee.com/ipvb 这个用户，想要创建一个自己的站点，但不想以子目录的方式访问，想以&lt;code&gt;ipvb.gitee.io&lt;/code&gt;直接访问，那么他就可以创建一个名字为&lt;code&gt;ipvb&lt;/code&gt;的仓库 https://gitee.com/ipvb/ipvb 部署完成后，就可以以 &lt;a href="https://ipvb.gitee.io/" target="_blank"&gt;https://ipvb.gitee.io&lt;/a&gt; 进行访问了。&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 23 Sep 2024 03:02:00 GMT</pubDate>
    </item>
    <item>
      <title>Centos7 将home空间分配给根目录（/dev/centos/root）</title>
      <link>https://maruifu.cn/article/321</link>
      <content:encoded>&lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;在安装centos系统的时候，如果在安装时没有分配磁盘空间，选择的是默认分配的，在安装完成后，可以发现大容量磁盘往往分配在了home下面。&lt;/li&gt; &lt;li&gt;在没有大量自定义用户使用的情况下，home的空间基本不用时，可以将空间转移至root下面。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h4&gt;查看目录结构&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server opt]# df -hl 文件系统                 容量  已用  可用 已用% 挂载点 devtmpfs                 7.7G     0  7.7G    0% /dev tmpfs                    7.7G     0  7.7G    0% /dev/shm tmpfs                    7.7G   18M  7.7G    1% /run tmpfs                    7.7G     0  7.7G    0% /sys/fs/cgroup /dev/mapper/centos-root   50G   39G   12G   78% / /dev/sda2               1014M  176M  839M   18% /boot /dev/sda1                200M   12M  189M    6% /boot/efi /dev/mapper/centos-home  180G   33M  180G    1% /home tmpfs                    1.6G     0  1.6G    0% /run/user/0 overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b13ed87897fe1cabe547341e9a855c43bf04fcf7f3ef44e41138b08e62fb6191/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/502140d31ceae249f65f90e2fc096af6d8cece5f086ab9ce7e01ff80ec82576d/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b8bfb2a4f4360f9b3915452fb4303d5ebf02299bee754de9d7245b5efc0d8149/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/59d0f795fa7c6d89467ada62d8e9e1cd8b9ad5628ca251bb5c9011fbed513af1/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b7ed9b03a812579f993ac7dbe086afb15401ec5109af9467a5efbbc524f74b79/merged   &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;备份/home数据&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;[root@mrf-server opt]# mkdir /home-backup [root@mrf-server opt]#  [root@mrf-server opt]# mv /home/* /home-backup &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;删除/home逻辑卷&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# umount /home [root@mrf-server /]#  [root@mrf-server /]# lvremove /dev/centos/home Do you really want to remove active logical volume centos/home? [y/n]: y   Logical volume &amp;quot;home&amp;quot; successfully removed  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查看可用大小&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# vgdisplay   --- Volume group ---   VG Name               centos   System ID                Format                lvm2   Metadata Areas        1   Metadata Sequence No  5   VG Access             read/write   VG Status             resizable   MAX LV                0   Cur LV                2   Open LV               2   Max PV                0   Cur PV                1   Act PV                1   VG Size               &amp;lt;237.28 GiB   PE Size               4.00 MiB   Total PE              60743   Alloc PE / Size       14784 / 57.75 GiB   Free  PE / Size       45959 / &amp;lt;179.53 GiB   VG UUID               6zqmne-yIRi-DzR0-pOc7-pwHI-O1hM-Vh7T5c   &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;根据：Free PE / Size 45959 / &amp;lt;179.53 GiB判断 可用空间为：179.53 GiB&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;新建/home卷&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# lvcreate -L 50G -n home centos WARNING: xfs signature detected on /dev/centos/home at offset 0. Wipe it? [y/n]: y   Wiping xfs signature on /dev/centos/home.   Logical volume &amp;quot;home&amp;quot; created.  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查看/home卷&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# lvdisplay   --- Logical volume ---   LV Path                /dev/centos/swap   LV Name                swap   VG Name                centos   LV UUID                PkUzOD-edvq-AYZH-RZte-nOOF-h27l-UDywl5   LV Write Access        read/write   LV Creation host, time localhost, 2024-05-07 16:07:38 +0800   LV Status              available   # open                 2   LV Size                7.75 GiB   Current LE             1984   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:1       --- Logical volume ---   LV Path                /dev/centos/root   LV Name                root   VG Name                centos   LV UUID                jpAxQq-GFzU-AfaT-Hotn-k6bG-FwII-a6EzIq   LV Write Access        read/write   LV Creation host, time localhost, 2024-05-07 16:07:43 +0800   LV Status              available   # open                 1   LV Size                50.00 GiB   Current LE             12800   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:0       --- Logical volume ---   LV Path                /dev/centos/home   LV Name                home   VG Name                centos   LV UUID                jMNrQ0-VpnG-FBry-wmrb-9oue-ALSZ-fuXYrd   LV Write Access        read/write   LV Creation host, time localhost.localdomain, 2024-05-08 09:38:46 +0800   LV Status              available   # open                 0   LV Size                50.00 GiB   Current LE             12800   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:2  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;分配50G空间成功&lt;/p&gt; &lt;h4&gt;查看可用空间大小&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# vgdisplay   --- Volume group ---   VG Name               centos   System ID                Format                lvm2   Metadata Areas        1   Metadata Sequence No  6   VG Access             read/write   VG Status             resizable   MAX LV                0   Cur LV                3   Open LV               2   Max PV                0   Cur PV                1   Act PV                1   VG Size               &amp;lt;237.28 GiB   PE Size               4.00 MiB   Total PE              60743   Alloc PE / Size       27584 / 107.75 GiB   Free  PE / Size       33159 / &amp;lt;129.53 GiB   VG UUID               6zqmne-yIRi-DzR0-pOc7-pwHI-O1hM-Vh7T5c   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;根据：Free PE / Size 33159 / &amp;lt;129.53 GiB判断 可用空间为129.53 GiB&lt;/p&gt; &lt;h4&gt;/home上建立xfs文件系统&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# mkfs -t xfs /dev/centos/home Discarding blocks...Done. meta-data=/dev/centos/home       isize=512    agcount=4, agsize=3276800 blks          =                       sectsz=512   attr=2, projid32bit=1          =                       crc=1        finobt=0, sparse=0 data     =                       bsize=4096   blocks=13107200, imaxpct=25          =                       sunit=0      swidth=0 blks naming   =version 2              bsize=4096   ascii-ci=0 ftype=1 log      =internal log           bsize=4096   blocks=6400, version=2          =                       sectsz=512   sunit=0 blks, lazy-count=1 realtime =none                   extsz=4096   blocks=0, rtextents=0   &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;挂载/home&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# mount /dev/centos/home /home [root@mrf-server /]# df -h 文件系统                 容量  已用  可用 已用% 挂载点 devtmpfs                 7.7G     0  7.7G    0% /dev tmpfs                    7.7G     0  7.7G    0% /dev/shm tmpfs                    7.7G   18M  7.7G    1% /run tmpfs                    7.7G     0  7.7G    0% /sys/fs/cgroup /dev/mapper/centos-root   50G   39G   12G   78% / /dev/sda2               1014M  176M  839M   18% /boot /dev/sda1                200M   12M  189M    6% /boot/efi tmpfs                    1.6G     0  1.6G    0% /run/user/0 overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b13ed87897fe1cabe547341e9a855c43bf04fcf7f3ef44e41138b08e62fb6191/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/502140d31ceae249f65f90e2fc096af6d8cece5f086ab9ce7e01ff80ec82576d/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b8bfb2a4f4360f9b3915452fb4303d5ebf02299bee754de9d7245b5efc0d8149/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/59d0f795fa7c6d89467ada62d8e9e1cd8b9ad5628ca251bb5c9011fbed513af1/merged overlay                   50G   39G   12G   78% /var/lib/docker/overlay2/b7ed9b03a812579f993ac7dbe086afb15401ec5109af9467a5efbbc524f74b79/merged /dev/mapper/centos-home   50G   33M   50G    1% /home  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;迁移home目录的备份数据&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;[root@mrf-server /]# mv /home-backup/* /home [root@mrf-server /]# rm -rf /home-backup &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;将剩余的空间分配给根目录&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]#  lvextend -L +129G /dev/centos/root   Size of logical volume centos/root changed from 50.00 GiB (12800 extents) to 179.00 GiB (45824 extents).   Logical volume centos/root successfully resized. [root@mrf-server /]#  [root@mrf-server /]#  [root@mrf-server /]#  [root@mrf-server /]#  [root@mrf-server /]# lvdisplay   --- Logical volume ---   LV Path                /dev/centos/swap   LV Name                swap   VG Name                centos   LV UUID                PkUzOD-edvq-AYZH-RZte-nOOF-h27l-UDywl5   LV Write Access        read/write   LV Creation host, time localhost, 2024-05-07 16:07:38 +0800   LV Status              available   # open                 2   LV Size                7.75 GiB   Current LE             1984   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:1       --- Logical volume ---   LV Path                /dev/centos/root   LV Name                root   VG Name                centos   LV UUID                jpAxQq-GFzU-AfaT-Hotn-k6bG-FwII-a6EzIq   LV Write Access        read/write   LV Creation host, time localhost, 2024-05-07 16:07:43 +0800   LV Status              available   # open                 1   LV Size                179.00 GiB   Current LE             45824   Segments               2   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:0       --- Logical volume ---   LV Path                /dev/centos/home   LV Name                home   VG Name                centos   LV UUID                jMNrQ0-VpnG-FBry-wmrb-9oue-ALSZ-fuXYrd   LV Write Access        read/write   LV Creation host, time localhost.localdomain, 2024-05-08 09:38:46 +0800   LV Status              available   # open                 1   LV Size                50.00 GiB   Current LE             12800   Segments               1   Allocation             inherit   Read ahead sectors     auto   - currently set to     256   Block device           253:2  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;扩展根目录&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;[root@mrf-server /]# xfs_growfs /dev/centos/root meta-data=/dev/mapper/centos-root isize=512    agcount=4, agsize=3276800 blks          =                       sectsz=512   attr=2, projid32bit=1          =                       crc=1        finobt=0 spinodes=0 data     =                       bsize=4096   blocks=13107200, imaxpct=25          =                       sunit=0      swidth=0 blks naming   =version 2              bsize=4096   ascii-ci=0 ftype=1 log      =internal               bsize=4096   blocks=6400, version=2          =                       sectsz=512   sunit=0 blks, lazy-count=1 realtime =none                   extsz=4096   blocks=0, rtextents=0 data blocks changed from 13107200 to 46923776 [root@mrf-server /]# df -h 文件系统                 容量  已用  可用 已用% 挂载点 devtmpfs                 7.7G     0  7.7G    0% /dev tmpfs                    7.7G     0  7.7G    0% /dev/shm tmpfs                    7.7G   18M  7.7G    1% /run tmpfs                    7.7G     0  7.7G    0% /sys/fs/cgroup /dev/mapper/centos-root  179G   39G  141G   22% / /dev/sda2               1014M  176M  839M   18% /boot /dev/sda1                200M   12M  189M    6% /boot/efi tmpfs                    1.6G     0  1.6G    0% /run/user/0 overlay                  179G   39G  141G   22% /var/lib/docker/overlay2/b13ed87897fe1cabe547341e9a855c43bf04fcf7f3ef44e41138b08e62fb6191/merged overlay                  179G   39G  141G   22% /var/lib/docker/overlay2/502140d31ceae249f65f90e2fc096af6d8cece5f086ab9ce7e01ff80ec82576d/merged overlay                  179G   39G  141G   22% /var/lib/docker/overlay2/b8bfb2a4f4360f9b3915452fb4303d5ebf02299bee754de9d7245b5efc0d8149/merged overlay                  179G   39G  141G   22% /var/lib/docker/overlay2/59d0f795fa7c6d89467ada62d8e9e1cd8b9ad5628ca251bb5c9011fbed513af1/merged overlay                  179G   39G  141G   22% /var/lib/docker/overlay2/b7ed9b03a812579f993ac7dbe086afb15401ec5109af9467a5efbbc524f74b79/merged /dev/mapper/centos-home   50G   33M   50G    1% /home   &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 15 Sep 2024 17:45:00 GMT</pubDate>
    </item>
    <item>
      <title>Mac环境下切换JDK版本</title>
      <link>https://maruifu.cn/article/320</link>
      <content:encoded>&lt;p&gt;Mac修改文件 &lt;code&gt;~/.bash_profile&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt; # 定义不同版本jdk的java_home export JAVA_8_HOME=/Users/maruifu/work/tools/java/jdk1.8.0_411.jdk/Contents/Home export JAVA_17_HOME=/Users/maruifu/work/tools/java/jdk-17.0.11.jdk/Contents/Home export JAVA_21_HOME=/Users/maruifu/work/tools/java/jdk-21.0.4.jdk/Contents/Home  # 给JAVA_HOME默认值  这个必须要有 export JAVA_HOME=$JAVA_17_HOME   # alias命令动态切换JDK版本 alias jdk8=&amp;quot;export JAVA_HOME=$JAVA_8_HOME&amp;quot; alias jdk17=&amp;quot;export JAVA_HOME=$JAVA_17_HOME&amp;quot; alias jdk21=&amp;quot;export JAVA_HOME=$JAVA_21_HOME&amp;quot;     PATH=$PATH:$JAVA_HOME/bin export PATH   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改完后，执行&lt;code&gt;source ~/.bash_profile&lt;/code&gt; 命令使改动生效&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maruifu@localhost ~ % source ~/.bash_profile maruifu@localhost ~ % java -version java version &amp;quot;17.0.11&amp;quot; 2024-04-16 LTS Java(TM) SE Runtime Environment (build 17.0.11+7-LTS-207) Java HotSpot(TM) 64-Bit Server VM (build 17.0.11+7-LTS-207, mixed mode, sharing) maruifu@localhost ~ % jdk8 maruifu@localhost ~ % java -version java version &amp;quot;1.8.0_411&amp;quot; Java(TM) SE Runtime Environment (build 1.8.0_411-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.411-b09, mixed mode) maruifu@localhost ~ % jdk21 maruifu@localhost ~ % java -version java version &amp;quot;21.0.4&amp;quot; 2024-07-16 LTS Java(TM) SE Runtime Environment (build 21.0.4+8-LTS-274) Java HotSpot(TM) 64-Bit Server VM (build 21.0.4+8-LTS-274, mixed mode, sharing) maruifu@localhost ~ % &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 12 Sep 2024 08:31:00 GMT</pubDate>
    </item>
    <item>
      <title>在群晖NAS中通过docker安装密码管理器bitwarden</title>
      <link>https://maruifu.cn/article/319</link>
      <content:encoded>&lt;p&gt;密码越来越多，网上，网下的，重要的，不重要的，如果每一个密码都设定不一样的，哪怕是大罗金仙也记不清楚了，所以，密码管理器就出现了，一个超级复杂的密码保护其他的所有密码——免费的密码管理器bitwarden就是这个领域的佼佼者，它的功能有账号密码管理、自动生成密码、导出密码、自动填充密码等等，简单而又实用。bitwarden搭配上一个永不关机的NAS则更是绝配，实现了数据本地化，使用实时化，而且功耗非常低。这里就介绍在群晖NAS上怎么通过docker实现本地部署bitwarden，&lt;strong&gt;这里有一个前置条件，那就是NAS要安装SSL证书，可以通过HTTPS进行加密访问。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Bitwarden 还可以私有化部署，这样可以确保数据完全掌握在自己手中，不必担心官方跑路。不过 Bit­war­den 官方服务对服务器需要的资源有点多，内存必须大于2G，小内存机器是根本跑不起来的，一般推荐使用第三方开发的 Vaultwarden。&lt;/p&gt; &lt;p&gt;Vaultwarden 是 Bitwarden 的轻量级版本，&lt;strong&gt;原名 bitwarden_rs&lt;/strong&gt;，后来为了与“大哥” Bitwarden 区分开来，遂改名为 Vaultwarden。&lt;/p&gt; &lt;p&gt;Vaultwarden 使用 Rust 编写，默认使用 SQLite 数据库（同时还支持 MySQL 和 PostgreSQL），实现了 Bit­war­den API 的所有功能，&lt;strong&gt;只需要 10M 内存便可运行&lt;/strong&gt;，几乎可以跑在任何硬件之上。https://github.com/dani-garcia/vaultwarden 不用想了，无脑使用 Vaultwarden 吧。&lt;/p&gt; &lt;h2&gt;准备工作&lt;/h2&gt; &lt;h3&gt;创建文件夹&lt;/h3&gt; &lt;p&gt;在DSM中使用file station创建两个文件夹：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/docker/vaultwarden/1.32.0  /docker/vaultwarden/1.32.0/ssl &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910143606657.png" alt="image-20240910143606657" title="image-20240910143606657" /&gt;&lt;/p&gt; &lt;h3&gt;证书导入&lt;/h3&gt; &lt;p&gt;在DSM中进入控制面板，找到“安全性”，&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910143936907.png" alt="image-20240910143936907" title="image-20240910143936907" /&gt;&lt;/p&gt; &lt;p&gt;然后在电脑上把下载的压缩文件“archive.zip”进行解压，获得两个证书文件：cert.pem和privkey.pem.&lt;/p&gt; &lt;p&gt;把这两个文件上传到/docker/vaultwarden/1.32.0/ssl文件夹中。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910144144955.png" alt="image-20240910144144955" title="image-20240910144144955" /&gt;&lt;/p&gt; &lt;h2&gt;开始安装&lt;/h2&gt; &lt;p&gt;打开docker，进入注册表，搜索vaultward，找到vaultward/server双击选择latest版本进行下载。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910144316892.png" alt="image-20240910144316892" title="image-20240910144316892" /&gt;&lt;/p&gt; &lt;p&gt;进入映像，双击下载的镜像vaultwarden/server：latest，开始安装容器。&lt;/p&gt; &lt;h3&gt;开机启动&lt;/h3&gt; &lt;p&gt;在安装设置中进入“高级设置” ，勾选“自动重新启动”。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910144929689.png" alt="image-20240910144929689" title="image-20240910144929689" /&gt;&lt;/p&gt; &lt;h3&gt;端口设置&lt;/h3&gt; &lt;p&gt;分别是 80端口 (API接口)和3012端口(  WebSocket 通知)&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910150640643.png" alt="image-20240910150640643" title="image-20240910150640643" /&gt;&lt;/p&gt; &lt;h3&gt;储存设置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910150701016.png" alt="image-20240910150701016" title="image-20240910150701016" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/docker/vaultwarden/1.32.0  --&amp;gt; /data /docker/vaultwarden/1.32.0/ssl  --&amp;gt; /ssl &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;环境设置&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910151403882.png" alt="image-20240910151403882" title="image-20240910151403882" /&gt;&lt;/p&gt; &lt;p&gt;增加 &lt;code&gt;ROCKET_TLS&lt;/code&gt;  (证书设置) 和后端管理密码 &lt;code&gt;ADMIN_TOKEN&lt;/code&gt;&lt;/p&gt; &lt;p&gt;新增一个变量&lt;code&gt;ROCKET_TLS&lt;/code&gt;，值是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;{certs=&amp;quot;/ssl/cert.pem&amp;quot;,key=&amp;quot;/ssl/privkey.pem&amp;quot;} &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;新增一个变量&lt;code&gt;ADMIN_TOKEN&lt;/code&gt;，值是：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-text"&gt;你自己的后台密码 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;后台管理&lt;/h2&gt; &lt;p&gt;在你的域名后面加上 /admin，登录 Vaultwarden 管理后台，登陆密码为刚刚设置的 ADMIN_TOKEN&lt;/p&gt; &lt;p&gt;在这里可以根据情况对 Vaultwarden 进行一些可选设置，所有的设置项都可以通过鼠标悬停查看相应的说明.&lt;/p&gt; &lt;h3&gt;一般设置(General Settings)&lt;/h3&gt; &lt;p&gt;1.Domain URL：设置你的网站域名，记得带上 https，如 https://your.domain&lt;/p&gt; &lt;p&gt;2.Allow new signups ：是否允许用户注册，如果密码库仅仅用于自用，建议在自己注册后关闭此选项。&lt;/p&gt; &lt;p&gt;3.Admin page token：在这里更改 Vaultwarden 管理后台的密码。&lt;/p&gt; &lt;p&gt;4.Invitation organization name：设置你的网站名字，将出现在自动发送的电子邮件中。&lt;/p&gt; &lt;h3&gt;邮箱设置(SMTP Email Settings)&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/image-20240910142741883.png" alt="image-20240910142741883" title="image-20240910142741883" /&gt;&lt;/p&gt; &lt;p&gt;1.设置 SMTP 服务，用来发送系统邮件（建议开启）。&lt;/p&gt; &lt;p&gt;2.你的 SMTP 服务提供方填写相关信息即可。&lt;/p&gt; &lt;p&gt;3.设置保存后，运行一次 Test SMTP 确保邮件可以正常发送。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Read-Only Config：这里可以查看所有只读选项。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Backup Database：这里提供了一个简易的数据库备份功能。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;其他配置&lt;/h3&gt; &lt;p&gt;Vaultwarden 可以通过环境变量来自定义各种配置，它的所有环境变量都在这个文件中：&lt;/p&gt; &lt;p&gt;https://github.com/dani-garcia/vaultwarden/blob/main/.env.template&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 10 Sep 2024 07:15:00 GMT</pubDate>
    </item>
    <item>
      <title>1267-Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLIC</title>
      <link>https://maruifu.cn/article/318</link>
      <content:encoded>&lt;h1&gt;现象&lt;/h1&gt; &lt;p&gt;今天LEFT JOIN 表，报如下错误： 1267-Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLIC for operation '=')&lt;/p&gt; &lt;p&gt;出现这种问题就是关联表的字符集不匹配&lt;/p&gt; &lt;h1&gt;原因&lt;/h1&gt; &lt;h2&gt;查看数据库的字符集&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;show variables where Variable_name like 'collation%'; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/10/8vuwsqmyav.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;查看关联表字符集&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; show create table table_name; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;给关联表设置字符编码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;alter table table_name default character set utf8mb4 collate=utf8mb4_general_ci; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;给关联表字段设置字符编码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; ALTER TABLE table_name convert to CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;转自于: https://cloud.tencent.com/developer/article/2190231&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 10 Sep 2024 02:00:31 GMT</pubDate>
    </item>
    <item>
      <title>【CentOS7.9】yum 报错：Could not retrieve mirrorlist http://mirrorlist.centos.org</title>
      <link>https://maruifu.cn/article/317</link>
      <content:encoded>&lt;h1&gt;一、报错&lt;/h1&gt; &lt;h2&gt;报错内容如下&lt;/h2&gt; &lt;p&gt;在使用 yum install -y xxx 的时候报错等等&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu yum.repos.d]# yum install net-tools.x86_64 Loaded plugins: fastestmirror Determining fastest mirrors Could not retrieve mirrorlist http://mirrorlist.centos.org?arch=x86_64&amp;amp;release=7&amp;amp;repo=sclo-rh error was 14: curl#6 - &amp;quot;Could not resolve host: mirrorlist.centos.org; Unknown error&amp;quot;    One of the configured repositories failed (Unknown),  and yum doesn't have enough cached data to continue. At this point the only  safe thing yum can do is fail. There are a few ways to work &amp;quot;fix&amp;quot; this:       1. Contact the upstream for the repository and get them to fix the problem.       2. Reconfigure the baseurl/etc. for the repository, to point to a working         upstream. This is most often useful if you are using a newer         distribution release than is supported by the repository (and the         packages for the previous distribution release still work).       3. Run the command with the repository temporarily disabled             yum --disablerepo=&amp;lt;repoid&amp;gt; ...       4. Disable the repository permanently, so yum won't use it by default. Yum         will then just ignore the repository until you permanently enable it         again or use --enablerepo for temporary usage:              yum-config-manager --disable &amp;lt;repoid&amp;gt;         or             subscription-manager repos --disable=&amp;lt;repoid&amp;gt;       5. Configure the failing repository to be skipped, if it is unavailable.         Note that yum will try to contact the repo. when it runs most commands,         so will have to try and fail each time (and thus. yum will be be much         slower). If it is a very temporary problem though, this is often a nice         compromise:              yum-config-manager --save --setopt=&amp;lt;repoid&amp;gt;.skip_if_unavailable=true  Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;原因分析&lt;/h2&gt; &lt;p&gt;我们的&lt;a href="https://so.csdn.net/so/search?q=服务器&amp;amp;spm=1001.2101.3001.7020" target="_blank"&gt;服务器&lt;/a&gt;没法访问外面的网络，所以我们需要配置镜像源&lt;/p&gt; &lt;h1&gt;二、解决&lt;/h1&gt; &lt;h2&gt;进入目录&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;cd /etc/yum.repos.d &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;备份文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 备份这三个文件就行了（注意！！很多人只有 CentOS-Base.repo 文件，没有后两个，这也没问题，有几个备份几个，不用额外创建！） CentOS-Base.repo  CentOS-SCLo-scl-rh.repo  CentOS-SCLo-scl.repo &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 编辑 CentOS-Base.repo 文件 vi /etc/yum.repos.d/CentOS-Base.repo #替换成以下内容 [base] name=CentOS-$releasever - Base baseurl=https://mirrors.aliyun.com/centos/$releasever/os/$basearch/ gpgcheck=1 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7  [updates] name=CentOS-$releasever - Updates baseurl=https://mirrors.aliyun.com/centos/$releasever/updates/$basearch/ gpgcheck=1 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7  [extras] name=CentOS-$releasever - Extras baseurl=https://mirrors.aliyun.com/centos/$releasever/extras/$basearch/ gpgcheck=1 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7  [centosplus] name=CentOS-$releasever - Plus baseurl=https://mirrors.aliyun.com/centos/$releasever/centosplus/$basearch/ gpgcheck=1 enabled=0 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt; # 编辑 CentOS-SCLo-scl-rh.repo 文件（如果没有这个文件，不用管） vi /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo #替换成以下内容 [centos-sclo-rh] name=CentOS-$releasever - SCLo rh baseurl=https://mirrors.aliyun.com/centos/$releasever/sclo/$basearch/rh/ gpgcheck=1 enabled=1 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;#编辑 CentOS-SCLo-scl.repo 文件（如果没有这个文件，不用管） vi /etc/yum.repos.d/CentOS-SCLo-scl.repo #替换成以下内容 [centos-sclo-sclo] name=CentOS-$releasever - SCLo sclo baseurl=https://mirrors.aliyun.com/centos/$releasever/sclo/$basearch/sclo/ gpgcheck=1 enabled=1 gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;清理 yum 缓存&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;yum clean all &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;重新生成 yum 缓存&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;yum makecache &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;请注意！如果在你之后的 yum install 过程中发生了 GPGKEY 报错，请参考这篇博客：&lt;/p&gt; &lt;p&gt;原文链接：https://blog.csdn.net/qq_43768851/article/details/140112143&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Tue, 10 Sep 2024 01:53:00 GMT</pubDate>
    </item>
    <item>
      <title>构建 RAG 系统的挑战</title>
      <link>https://maruifu.cn/article/316</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/05/118694f5a34208e52234dd9c12e898f347b4e2d7-1199x627.png" alt="掌握 RAG：如何构建企业 RAG 系统" title="掌握 RAG：如何构建企业 RAG 系统" /&gt;&lt;/p&gt; &lt;p&gt;探索我们基于研究的 RAG 评估指标——阅读我们关于 &lt;a href="https://arxiv.org/abs/2310.18344" target="_blank"&gt;Chainpoll&lt;/a&gt;的论文。&lt;/p&gt; &lt;p&gt;欢迎回到我们的 Mastering RAG 系列！让我们撸起袖子，深入研究构建企业&lt;a href="https://www.rungalileo.io/blog/mastering-rag-how-to-architect-an-enterprise-rag-system" target="_blank"&gt;RAG&lt;/a&gt;系统的复杂世界。&lt;/p&gt; &lt;p&gt;尽管互联网上充斥着有关简单 RAG 系统的文章，但构建强大的企业级解决方案的历程往往是一个谜。&lt;/p&gt; &lt;p&gt;但这篇博客不仅仅是一次理论之旅；它是帮助您采取行动的实用指南！从护栏在确保安全方面的重要性到查询重写对用户体验的影响，我们在这里提供可操作的见解和真实示例。无论您是经验丰富的开发人员还是掌舵的技术领导者，系好安全带，深入探索尖端企业 RAG 的错综复杂的世界！&lt;/p&gt; &lt;p&gt;&lt;img src="https://cdn.sanity.io/images/tf66morw/production/11ecc7741c440f92eaef444027435a5de6e21743-2152x1356.gif?w=2152&amp;amp;h=1356&amp;amp;auto=format" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;在介绍 RAG 架构之前，我想先分享一项关于构建 RAG 系统时常见故障点的最新&lt;a href="https://arxiv.org/abs/2401.05856" target="_blank"&gt;研究&lt;/a&gt;。研究人员分析了三个跨不同领域的案例研究，发现了七个常见的 RAG 故障点。&lt;/p&gt; &lt;h2&gt;构建 RAG 系统的挑战&lt;/h2&gt; &lt;h3&gt;案例研究&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/12815f255734d2c01d3c370350c006709b2aca98-2078x454.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h4&gt;认知审阅者&lt;/h4&gt; &lt;p&gt;认知审阅器是一种 RAG 系统，旨在协助研究人员分析科学文献。研究人员定义研究问题或目标，并上传相关研究论文集。然后，系统根据所述目标对所有文档进行排序，以供研究人员进行人工审阅。此外，研究人员可以直接针对整个文档集提出问题。&lt;/p&gt; &lt;h4&gt;人工智能导师&lt;/h4&gt; &lt;p&gt;另一个 RAG 系统 AI Tutor 允许学生就某个单元提问，并根据学习内容获得答案。学生可以通过访问来源列表来验证答案。AI Tutor 集成到迪肯的学习管理系统中，索引所有内容，包括 PDF、视频和文本文档。该系统使用 Whisper 深度学习模型转录视频，然后再进行分块。RAG 管道包含一个用于查询泛化的重写器，聊天界面利用过去的对话作为每个问题的上下文。&lt;/p&gt; &lt;h4&gt;生物医学问答&lt;/h4&gt; &lt;p&gt;在生物医学问答案例研究中，使用 BioASQ 数据集创建了一个 RAG 系统，其中包含问题、文档链接和答案。该数据集由生物医学专家准备，包括特定领域的问答对。问题的答案可以是是/否、文本摘要、事实或列表。&lt;/p&gt; &lt;h3&gt;RAG 系统的 7 个故障点&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/a50df3df3f7a4fc4b184c8f7cd7a9974c7146f4c-2130x970.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;通过这些案例研究，他们确定了设计 RAG 系统时经常出现的七个故障点。&lt;/p&gt; &lt;h4&gt;缺失内容（FP1）&lt;/h4&gt; &lt;p&gt;提出的问题无法通过现有文档回答。在理想情况下，RAG 系统会回复“抱歉，我不知道”之类的消息。但是，对于与内容相关的问题，如果没有明确的答案，系统可能会被误导而提供答案。&lt;/p&gt; &lt;h4&gt;错过了排名靠前的文档（FP2）&lt;/h4&gt; &lt;p&gt;文档中存在问题的答案，但排名不够高，无法包含在返回给用户的结果中。虽然理论上所有文档都会被排名并在后续步骤中使用，但实际上只会返回排名前 K 的文档，其中 K 是根据性能选择的值。&lt;/p&gt; &lt;h4&gt;不在上下文中 - 合并策略限制（FP3）&lt;/h4&gt; &lt;p&gt;从数据库中检索到包含答案的文档，但无法将其放入生成响应的上下文中。当返回大量文档时，就会发生这种情况，从而导致合并过程，从而阻碍相关答案的检索。&lt;/p&gt; &lt;h4&gt;未提取（FP4）&lt;/h4&gt; &lt;p&gt;答案存在于上下文中，但模型无法提取正确的信息。这种情况通常发生在上下文中存在过多噪音或相互矛盾的信息时。&lt;/p&gt; &lt;h4&gt;格式错误（FP5）&lt;/h4&gt; &lt;p&gt;问题涉及以特定格式（例如表格或列表）提取信息，而模型忽略了该指令。&lt;/p&gt; &lt;h4&gt;特异性不正确（FP6）&lt;/h4&gt; &lt;p&gt;响应包含答案，但缺乏所需的特异性或过于具体，无法满足用户的需求。当 RAG 系统设计人员对给定问题有预先确定的结果时，例如教师寻求教育内容，就会发生这种情况。在这种情况下，应与答案一起提供具体的教育内容。当用户不确定如何表述问题并且过于笼统时，也会出现不正确的特异性。&lt;/p&gt; &lt;h4&gt;不完整（FP7）&lt;/h4&gt; &lt;p&gt;不完整的答案是准确的，但缺少一些信息，即使这些信息存在于上下文中并且可以提取。例如，对于“文档 A、B 和 C 中涵盖的要点是什么？”这样的问题，最好分别提出这些问题。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/ea880ca2ef2f6f5e0ac13172ef234e771677c828-2184x1238.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;下表列出了他们从解决每个问题中吸取的教训。在构建我们的企业 RAG 系统时，我们将牢记这些教训。&lt;/p&gt; &lt;h2&gt;如何构建企业 RAG 系统&lt;/h2&gt; &lt;p&gt;现在我们已经确定了设计 RAG 系统时面临的常见问题，让我们来看看每个组件的设计需求和作用，以及构建它们的最佳实践。上面的 RAG 系统架构图提供了每个组件的使用位置和方式的背景信息。&lt;/p&gt; &lt;h3&gt;用户身份验证&lt;/h3&gt; &lt;p&gt;一切从这里开始——我们系统中的第一个组件！在用户开始与聊天机器人交互之前，我们需要出于各种原因对用户进行身份验证。身份验证有助于实现安全性和个性化，这对于企业系统来说是必不可少的。&lt;/p&gt; &lt;h4&gt;访问控制&lt;/h4&gt; &lt;p&gt;身份验证可确保只有授权用户才能访问系统。它有助于控制谁可以与系统交互以及他们可以执行哪些操作。&lt;/p&gt; &lt;h4&gt;数据安全&lt;/h4&gt; &lt;p&gt;保护敏感数据至关重要。用户身份验证可防止未经授权的个人访问机密信息，从而防止数据泄露和未经授权的数据操纵。&lt;/p&gt; &lt;h4&gt;用户隐私&lt;/h4&gt; &lt;p&gt;身份验证可确保只有目标用户才能访问其个人信息和帐户详细信息，从而帮助维护用户隐私。这对于与用户建立信任至关重要。&lt;/p&gt; &lt;h4&gt;法律合规&lt;/h4&gt; &lt;p&gt;许多司法管辖区和行业都有法规和法律，要求组织实施适当的用户身份验证以保护用户数据和隐私。遵守这些法规有助于避免法律问题和潜在的处罚。&lt;/p&gt; &lt;h4&gt;问责制&lt;/h4&gt; &lt;p&gt;身份验证通过将系统内的操作与特定用户帐户绑定来确保责任的可追溯性。这对于审计和跟踪用户活动至关重要，有助于识别和解决任何安全事件或可疑行为。&lt;/p&gt; &lt;h4&gt;个性化和定制&lt;/h4&gt; &lt;p&gt;身份验证使系统能够识别单个用户，从而实现用户体验的个性化和定制化。这可以包括定制的内容、偏好和设置。&lt;/p&gt; &lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=vqAirwfYgrY" target="_blank"&gt;AWS Cognito&lt;/a&gt;或&lt;a href="https://www.youtube.com/watch?v=vBUk293QSKY" target="_blank"&gt;Firebase Authentication&lt;/a&gt;等服务可以帮助您轻松地将用户注册和身份验证添加到移动和 Web 应用程序。&lt;/p&gt; &lt;h3&gt;输入护栏&lt;/h3&gt; &lt;p&gt;防止用户输入可能有害或包含私人信息的信息至关重要。最近的研究表明，&lt;a href="https://llm-attacks.org/" target="_blank"&gt;越狱 LLM&lt;/a&gt;很容易。这就是输入护栏的作用所在。让我们看看我们需要护栏的不同场景。&lt;/p&gt; &lt;h4&gt;匿名化&lt;/h4&gt; &lt;p&gt;输入护栏可以匿名化或删除个人身份信息 (PII)，例如姓名、地址或联系方式。这有助于保护隐私并防止恶意泄露敏感信息。&lt;/p&gt; &lt;h4&gt;限制子字符串&lt;/h4&gt; &lt;p&gt;禁止可能被用于 SQL 注入、跨站点脚本 (XSS) 或其他注入攻击的某些子字符串或模式，可防止安全漏洞或不良行为。&lt;/p&gt; &lt;h4&gt;限制主题&lt;/h4&gt; &lt;p&gt;为了限制与特定主题相关的可能不适当、冒犯或违反社区准则的讨论或输入，过滤掉涉及仇恨言论、歧视或露骨内容的内容非常重要。&lt;/p&gt; &lt;h4&gt;限制代码&lt;/h4&gt; &lt;p&gt;防止可能危害系统安全或导致代码注入攻击的可执行代码的注入至关重要。&lt;/p&gt; &lt;h4&gt;限制语言&lt;/h4&gt; &lt;p&gt;验证文本输入是否采用正确的语言或脚本，以防止潜在的误解或处理错误。&lt;/p&gt; &lt;h4&gt;检测即时注入&lt;/h4&gt; &lt;p&gt;减轻注入误导性或有害提示的尝试，这些提示可能会操纵系统或以非预期的方式影响 LLM 的行为。&lt;/p&gt; &lt;h4&gt;限制代币&lt;/h4&gt; &lt;p&gt;对用户输入实施最大令牌或字符限制有助于避免资源耗尽并防止拒绝服务 (DoS) 攻击。&lt;/p&gt; &lt;h4&gt;检测毒性&lt;/h4&gt; &lt;p&gt;实施毒性过滤器来识别和阻止包含有害或辱骂性语言的输入。&lt;/p&gt; &lt;p&gt;为了保护您的 RAG 系统免受这些情况的影响，您可以利用Meta 的&lt;a href="https://towardsdatascience.com/safeguarding-your-rag-pipelines-a-step-by-step-guide-to-implementing-llama-guard-with-llamaindex-6f80a2e07756" target="_blank"&gt;Llama Guard 。您可以自行托管它，也可以使用&lt;/a&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/llama-guard-is-now-available-in-amazon-sagemaker-jumpstart/" target="_blank"&gt;Sagemaker&lt;/a&gt;等托管服务。但是，不要指望它在检测毒性方面是完美的。&lt;/p&gt; &lt;h3&gt;查询重写器&lt;/h3&gt; &lt;p&gt;一旦查询通过了输入护栏，我们就会将其发送给查询重写器。有时，用户查询可能很模糊，或者需要上下文才能更好地理解用户的意图。查询重写是一种有助于解决此问题的技术。它涉及转换用户查询以增强清晰度、准确性和相关性。让我们来看看一些最流行的技术。&lt;/p&gt; &lt;h4&gt;根据历史重写&lt;/h4&gt; &lt;p&gt;在这种方法中，系统利用用户的查询历史来了解对话的背景并增强后续查询。让我们以信用卡查询为例。&lt;/p&gt; &lt;p&gt;查询历史记录：&lt;/p&gt; &lt;p&gt;“您有几张信用卡？”&lt;/p&gt; &lt;p&gt;“白金信用卡和金卡有年费吗？”&lt;/p&gt; &lt;p&gt;“比较两者的特点。”&lt;/p&gt; &lt;p&gt;我们必须根据用户的查询历史来识别上下文的演变，辨别用户的意图和查询之间的关系，并生成与演变上下文相一致的查询。&lt;/p&gt; &lt;p&gt;重写查询：“比较白金卡和金卡的特点。”&lt;/p&gt; &lt;h4&gt;创建子查询&lt;/h4&gt; &lt;p&gt;由于检索问题，复杂的查询可能难以回答。为了简化任务，查询被分解为更具体的子查询。这有助于检索生成答案所需的正确上下文。LlamaIndex 将此称为&lt;a href="https://docs.llamaindex.ai/en/stable/examples/query_engine/sub_question_query_engine.html" target="_blank"&gt;子问题查询引擎&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;给定查询“比较白金信用卡和金卡的特点”，系统会为每张卡生成子查询，重点关注原始查询中提到的各个实体。&lt;/p&gt; &lt;p&gt;重写子查询：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;“白金信用卡有什么特点？”&lt;/li&gt; &lt;li&gt;“金卡信用卡有什么特点？”&lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;创建类似查询&lt;/h4&gt; &lt;p&gt;为了增加检索到正确文档的机会，我们根据用户输入生成类似的查询。这是为了克服检索在语义或词汇匹配方面的局限性。&lt;/p&gt; &lt;p&gt;如果用户询问信用卡功能，系统会生成相关查询。使用同义词、相关术语或特定领域知识来创建符合用户意图的查询。&lt;/p&gt; &lt;p&gt;生成的类似查询：&lt;/p&gt; &lt;p&gt;“我想了解白金信用卡” -&amp;gt; “请告诉我白金信用卡的好处。”&lt;/p&gt; &lt;h3&gt;编码器&lt;/h3&gt; &lt;p&gt;一旦我们有了原始查询和重写查询，我们就会将它们编码为向量（数字列表）以供检索。选择编码器可能是构建 RAG 系统时最重要的决定。 让我们 探讨一下选择文本编码器的原因以及需要考虑的因素。&lt;/p&gt; &lt;h4&gt;利用 MTEB 基准&lt;/h4&gt; &lt;p&gt;要全面评估编码器的功能，首选来源是&lt;a href="https://huggingface.co/spaces/mteb/leaderboard" target="_blank"&gt;Massive Text Embedding Benchmark&lt;/a&gt; (MTEB)。此基准允许根据向量维度、平均检索性能和模型大小对编码器进行细致的选择。虽然 MTEB 提供了有价值的见解，但必须以一定程度的怀疑态度对待结果，因为没有一刀切的评估基准，并且模型训练数据的细节可能未完全披露。&lt;/p&gt; &lt;p&gt;MTEB 不仅提供了对 OpenAI、Cohere 和 Voyager 等流行嵌入性能的洞察，还揭示了某些开源模型表现出接近的性能水平。但是，需要注意的是，这些结果提供了一般概述，可能无法准确表明这些嵌入在您的域的特定上下文中的表现如何。因此，在做出最终选择之前，必须对您的数据集进行彻底的评估，强调自定义评估方法的重要性。&lt;/p&gt; &lt;h4&gt;定制评估&lt;/h4&gt; &lt;p&gt;编码器可能无法始终提供最佳性能，尤其是在处理敏感信息时。在这种情况下，&lt;a href="https://www.rungalileo.io/blog/mastering-rag-improve-performance-with-4-powerful-metrics" target="_blank"&gt;自定义评估方法变得至关重要。以下是执行自定义评估的三种方法。&lt;/a&gt;&lt;/p&gt; &lt;p&gt;通过注释进行评估&lt;/p&gt; &lt;p&gt;生成专用数据集并设置注释以获得黄金标签。注释后，利用平均倒数排名 (MRR) 和正则化折扣累积增益 (NDCG) 等检索指标定量评估不同编码器的性能。&lt;/p&gt; &lt;p&gt;通过模型评估&lt;/p&gt; &lt;p&gt;遵循与注释方法类似的数据生成过程，但使用 LLM 或交叉编码器作为评估器。这允许在所有编码器之间建立相对排名。随后，对前三名编码器进行手动评估可以得出精确的性能指标。&lt;/p&gt; &lt;p&gt;通过聚类进行评估&lt;/p&gt; &lt;p&gt;采用不同的聚类技术，并分析不同 Silhouette 分数下的覆盖率（聚类数据量），表明聚类内的向量相似性。使用 HDBSCAN 等算法进行实验，调整其参数以获得最佳性能选择。这种基于聚类的评估提供了有关数据点分布和分组的宝贵见解，有助于选择符合特定指标的编码器。&lt;/p&gt; &lt;h4&gt;选择文本编码器的考虑因素&lt;/h4&gt; &lt;p&gt;在选择编码器时，您需要在私有编码器和公共编码器之间做出选择。您可能会倾向于使用私有编码器，因为它易于使用，但这两种选择之间需要权衡一些特定的利弊。这是一个重要的决定，它将决定您的系统的性能和延迟。&lt;/p&gt; &lt;p&gt;查询费用&lt;/p&gt; &lt;p&gt;确保语义搜索的流畅用户体验依赖于嵌入 API 服务的高可用性。OpenAI 和类似提供商提供可靠的 API，无需托管管理。然而，选择开源模型需要根据模型大小和延迟需求进行工程设计。较小的模型（最多 1.1 亿个参数）可以使用 CPU 实例托管，而较大的模型可能需要 GPU 服务来满足延迟要求。&lt;/p&gt; &lt;p&gt;索引成本&lt;/p&gt; &lt;p&gt;设置语义搜索涉及索引文档，这会产生不小的成本。由于索引和查询共享同一个编码器，因此索引成本取决于所选的编码器服务。为了方便服务重置或重新索引到备用向量数据库，建议单独存储嵌入。忽略此步骤将需要重新计算相同的嵌入。&lt;/p&gt; &lt;p&gt;存储成本&lt;/p&gt; &lt;p&gt;对于索引数百万个向量的应用程序，Vector DB 存储成本是一个至关重要的考虑因素。存储成本与维度成线性关系，OpenAI 的 1526 维嵌入会产生最大的存储成本。要估算存储成本，请计算每个文档的平均单位（短语或句子）并进行推断。&lt;/p&gt; &lt;p&gt;语言支持&lt;/p&gt; &lt;p&gt;为了支持您的非英语语言，请使用多语言编码器或使用翻译系统和英语编码器。&lt;/p&gt; &lt;p&gt;搜索延迟&lt;/p&gt; &lt;p&gt;语义搜索的延迟随嵌入的维度线性增长。选择较低维度的嵌入是最小化延迟的首选。&lt;/p&gt; &lt;p&gt;隐私&lt;/p&gt; &lt;p&gt;金融和医疗保健等敏感领域的严格数据隐私要求可能会降低 OpenAI 等服务的可行性。&lt;/p&gt; &lt;h3&gt;文档摄取&lt;/h3&gt; &lt;p&gt;文档摄取系统管理数据的处理和持久性。在索引过程中，每个文档被拆分成较小的块，然后使用嵌入模型将其转换为嵌入。然后将原始块和嵌入在数据库中进行索引。让我们看看文档摄取系统的组件。&lt;/p&gt; &lt;h4&gt;文档解析器&lt;/h4&gt; &lt;p&gt;文档解析器在从各种文档格式中主动提取结构化信息方面发挥着核心作用，尤其注重格式处理。这包括但不限于解析可能包含图像和表格的 PDF。&lt;/p&gt; &lt;p&gt;文档格式&lt;/p&gt; &lt;p&gt;文档解析器必须能够熟练处理各种文档格式，例如 PDF、Word、Excel 等，确保文档处理的适应性。这涉及识别和管理嵌入内容，例如超链接、多媒体元素或注释，以提供文档的全面表示。&lt;/p&gt; &lt;p&gt;表格识别&lt;/p&gt; &lt;p&gt;识别和提取文档中表格中的数据对于维护信息结构至关重要，尤其是在报告或研究论文中。提取与表格相关的元数据（包括标题、行和列信息）可增强对文档组织结构的理解。诸如&lt;a href="https://huggingface.co/spaces/nielsr/tatr-demo" target="_blank"&gt;Table Transformer&lt;/a&gt;之类的模型可用于完成此任务。&lt;/p&gt; &lt;p&gt;图像识别&lt;/p&gt; &lt;p&gt;OCR 应用于文档中的图像，以主动识别和提取文本，以便进行索引和后续检索。&lt;/p&gt; &lt;p&gt;元数据提取&lt;/p&gt; &lt;p&gt;元数据是指文档的主要内容之外的附加信息。它包括作者、创建日期、文档类型、关键字等详细信息。元数据提供了有价值的背景信息，有助于组织文档并通过考虑元数据属性来提高搜索结果的相关性。可以使用 NLP/OCR 管道提取元数据，并将其作为特殊字段与文档一起编入索引。&lt;/p&gt; &lt;h3&gt;Chunker&lt;/h3&gt; &lt;p&gt;如何决定标记（拆分）长文本可以决定嵌入的质量和搜索系统的性能。如果块太小，某些问题就无法回答；如果块太长，那么答案就会包含生成的噪音。您可以利用&lt;a href="https://www.youtube.com/watch?v=qaPMdcCqtWk" target="_blank"&gt;摘要&lt;/a&gt;技术来减少噪音、文本大小、编码成本和存储成本。&lt;/p&gt; &lt;p&gt;分块是一个重要但被低估的话题。它可能需要与特征工程类似的领域专业知识。例如，python 代码库的分块可能使用 def/class 之类的前缀来完成。阅读我们的博客以&lt;a href="https://www.rungalileo.io/blog/mastering-rag-advanced-chunking-techniques-for-llm-applications" target="_blank"&gt;深入了解分块&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/9f74cf3fa45aa3c2cde2e0dcc7e7e285dd3ec5c1-2398x1254.png" alt="RAG 的分块技术" title="RAG 的分块技术" /&gt;&lt;/p&gt; &lt;p&gt;RAG 的分块技术&lt;/p&gt; &lt;h3&gt;索引器&lt;/h3&gt; &lt;p&gt;索引器 – 您猜对了 – 负责创建文档的索引，该索引用作结构化数据结构（快说 3 遍……）。索引器有助于实现高效的搜索和检索操作。高效的索引对于快速准确地检索文档至关重要。它涉及将块或标记映射到文档集合中的相应位置。索引器在文档检索中执行重要任务，包括创建索引以及添加、更新或删除文档。&lt;/p&gt; &lt;p&gt;索引器作为 RAG 系统的关键组件，面临着各种挑战和问题，这些挑战和问题会影响系统的整体效率和性能。&lt;/p&gt; &lt;h4&gt;可扩展性问题&lt;/h4&gt; &lt;p&gt;随着文档数量的增长，保持高效快速的索引变得具有挑战性。当系统难以处理越来越多的文档时，可能会出现可扩展性问题，从而导致索引和检索时间变慢。&lt;/p&gt; &lt;h4&gt;实时指数更新&lt;/h4&gt; &lt;p&gt;实时更新索引可能是一项挑战，尤其是在频繁添加、更新或删除文档的系统中。确保实时 API 和实时索引机制无缝运行且不影响系统性能是一项长期挑战。&lt;/p&gt; &lt;h4&gt;一致性和原子性&lt;/h4&gt; &lt;p&gt;在文档同时更新或修改的情况下实现一致性和原子性可能非常复杂。确保索引更新保持数据完整性（即使在同时发生更改的情况下）需要仔细的设计和实施。&lt;/p&gt; &lt;h4&gt;优化存储空间&lt;/h4&gt; &lt;p&gt;索引大量文档可能会产生大量的存储需求。优化存储空间，同时确保索引保持可访问性和响应性，这是一个持续的挑战，尤其是在存储成本令人担忧的情况下。&lt;/p&gt; &lt;h4&gt;安全和访问控制&lt;/h4&gt; &lt;p&gt;实施适当的安全措施和访问控制以防止未经授权修改索引至关重要。确保只有授权用户或流程才能执行 CRUD 操作有助于保护文档存储库的完整性。&lt;/p&gt; &lt;h4&gt;监控和维护&lt;/h4&gt; &lt;p&gt;定期监控索引器的运行状况和性能至关重要。检测索引失败、资源瓶颈或索引过时等问题需要强大的监控和维护程序，以确保系统长期平稳运行。&lt;/p&gt; &lt;p&gt;这些是一些困难但众所周知的软件工程挑战，可以通过遵循良好的软件设计实践来解决。&lt;/p&gt; &lt;h3&gt;数据存储&lt;/h3&gt; &lt;p&gt;由于我们要处理各种数据，因此我们需要为每种数据提供专用存储。了解每种存储类型的不同注意事项以及每种存储类型的具体用例至关重要。&lt;/p&gt; &lt;h4&gt;嵌入&lt;/h4&gt; &lt;p&gt;数据库类型：SQL/NoSQL&lt;/p&gt; &lt;p&gt;单独存储文档嵌入可以快速重新索引，而无需重新计算整个文档语料库的嵌入。此外，嵌入存储可充当备份，确保即使在系统发生故障或更新时也能保留关键信息。&lt;/p&gt; &lt;h4&gt;文件&lt;/h4&gt; &lt;p&gt;数据库类型：NoSQL&lt;/p&gt; &lt;p&gt;以原始格式存储文档对于持久存储至关重要。此原始格式是各种处理阶段（例如索引、解析和检索）的基础。它还为未来的系统增强提供了灵活性，因为原始文档保持完整并可根据需要重新处理。&lt;/p&gt; &lt;h4&gt;聊天记录&lt;/h4&gt; &lt;p&gt;数据库类型：NoSQL&lt;/p&gt; &lt;p&gt;聊天记录的存储对于支持 RAG 系统的对话功能至关重要。聊天记录存储允许系统回忆以前的用户查询、响应和偏好，使其能够根据用户的独特背景调整和定制未来的交互。这些历史数据是一种宝贵的资源，可以利用它进行研究来改进 ML 系统。&lt;/p&gt; &lt;h4&gt;用户反馈&lt;/h4&gt; &lt;p&gt;数据库类型：NoSQL/SQL&lt;/p&gt; &lt;p&gt;通过 RAG 应用程序内的各种交互机制系统地收集用户反馈。在大多数 LLM 系统中，用户可以使用点赞/点踩、星级评定和文本反馈来提供反馈。这一系列用户见解是一个宝贵的存储库，它囊括了用户体验和感知，为持续的系统增强奠定了基础。&lt;/p&gt; &lt;h3&gt;矢量数据库&lt;/h3&gt; &lt;p&gt;支持语义搜索的向量数据库是 RAG 的关键检索组件。然而，正确选择此组件对于避免潜在问题至关重要。在选择过程中需要考虑几个&lt;a href="https://vdbs.superlinked.com/" target="_blank"&gt;向量数据库因素&lt;/a&gt;。让我们来了解一下其中的一些。&lt;/p&gt; &lt;h4&gt;召回率与延迟&lt;/h4&gt; &lt;p&gt;在矢量数据库中，优化召回率（相关结果的百分比）与延迟（返回结果的时间）是一种权衡。Flat、HNSW（分层可导航小世界）、PQ（产品量化）、ANNOY 和 DiskANN 等不同指标&lt;a href="https://github.com/facebookresearch/faiss/wiki/Faiss-indexes#flat-indexes" target="_blank"&gt;在&lt;/a&gt;速度&lt;a href="https://weaviate.io/blog/ann-algorithms-vamana-vs-hnsw" target="_blank"&gt;和&lt;/a&gt;召回&lt;a href="https://www.pinecone.io/learn/series/faiss/product-quantization/" target="_blank"&gt;率&lt;/a&gt;之间&lt;a href="https://erikbern.com/2015/09/24/nearest-neighbor-methods-vector-models-part-1" target="_blank"&gt;做出&lt;/a&gt;了&lt;a href="https://zilliz.com/learn/DiskANN-and-the-Vamana-Algorithm" target="_blank"&gt;不同&lt;/a&gt;的&lt;a href="https://ann-benchmarks.com/" target="_blank"&gt;权衡&lt;/a&gt;。对您的数据和查询进行基准研究，以做出明智的决策。&lt;/p&gt; &lt;h4&gt;成本&lt;/h4&gt; &lt;p&gt;采用托管解决方案的云原生数据库通常根据数据存储和查询量计费。此模式适用于拥有大量数据的组织，可避免基础设施成本。主要考虑因素包括评估数据集增长、团队能力、数据敏感性以及了解托管云解决方案的成本影响。&lt;/p&gt; &lt;p&gt;另一方面，自托管使组织能够更好地控制其基础架构，并可能降低成本。但是，它伴随着管理和维护基础架构的责任，包括考虑可扩展性、安全性和更新。&lt;/p&gt; &lt;h4&gt;插入速度与查询速度&lt;/h4&gt; &lt;p&gt;平衡插入速度和查询速度至关重要。寻找能够处理具有高插入速度要求的流式用例的供应商。但是，对于大多数组织来说，优先考虑查询速度更为重要。评估峰值负载下的向量插入速度查询延迟，以做出明智的决策。&lt;/p&gt; &lt;h4&gt;内存与磁盘索引存储&lt;/h4&gt; &lt;p&gt;在内存和磁盘存储之间进行选择涉及速度和成本的权衡。虽然内存存储提供了高速，但有些用例需要存储比内存更大的向量。内存映射文件等技术允许扩展向量存储而不会影响搜索速度。DiskANN 中的 Vamana 等新索引可实现高效的内存外索引。&lt;/p&gt; &lt;h4&gt;全文搜索与向量混合搜索&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/414c9c8001526d75163bce13b5a367683f1b2b00-2640x608.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;来源：&lt;a href="https://www.pinecone.io/learn/hybrid-search-intro/" target="_blank"&gt;https://www.pinecone.io/learn/hybrid-search-intro/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;单独的向量搜索可能不是企业级应用程序的最佳选择。另一方面，混合搜索集成了密集和稀疏方法，需要额外的努力。实现密集向量索引、稀疏倒排索引和重新排序步骤是典型的做法。密集和稀疏元素之间的平衡可以通过&lt;a href="https://www.pinecone.io/learn/hybrid-search-intro/" target="_blank"&gt;Pinecone&lt;/a&gt;、&lt;a href="https://weaviate.io/blog/hybrid-search-fusion-algorithms" target="_blank"&gt;Weaviate&lt;/a&gt;和&lt;a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid" target="_blank"&gt;Elasticsearch&lt;/a&gt;中称为 alpha 的参数进行调整。&lt;/p&gt; &lt;h4&gt;过滤&lt;/h4&gt; &lt;p&gt;实际的搜索查询通常涉及对元数据属性的过滤。预过滤搜索虽然看似自然，但可能会导致相关结果丢失。如果过滤后的属性只是数据集的一小部分，则后过滤搜索可能会出现问题。自定义过滤搜索（如&lt;a href="https://weaviate.io/developers/weaviate/concepts/prefiltering" target="_blank"&gt;Weaviate&lt;/a&gt;）将预过滤与使用倒排索引分片和 HNSW 索引分片的有效语义搜索相结合。&lt;/p&gt; &lt;h3&gt;改进检索的技术&lt;/h3&gt; &lt;p&gt;最近的研究表明，&lt;a href="https://arxiv.org/abs/2302.00093" target="_blank"&gt;LLM 很容易被不相关的上下文分散注意力&lt;/a&gt;，并且由于 LLM 的注意力模式，拥有大量上下文（topK 检索文档）可能会导致&lt;a href="https://arxiv.org/abs/2307.03172" target="_blank"&gt;遗漏某些上下文&lt;/a&gt;。因此，使用相关且多样化的文档来改进检索至关重要。让我们来看看一些经过验证的改进检索的技术。&lt;/p&gt; &lt;h4&gt;假设文档嵌入（HyDE）&lt;/h4&gt; &lt;p&gt;我们可以使用&lt;a href="https://arxiv.org/abs/2212.10496" target="_blank"&gt;HyDE&lt;/a&gt;技术来解决检索性能不佳的问题，尤其是在处理简短或不匹配的查询时，这些查询会使查找信息变得困难。HyDE 采用一种独特的方法，使用由 GPT 等模型创建的假设文档。这些假设文档捕捉了重要的模式，但可能包含虚构或不正确的细节。然后，智能文本编码器将此假设文档转换为向量嵌入。与查询嵌入相比，这种嵌入有助于在集合中找到类似的真实文档。&lt;/p&gt; &lt;p&gt;实验表明，HyDE 比其他先进方法效果更好，使其成为提升 RAG 系统性能的有用工具。&lt;/p&gt; &lt;h4&gt;查询路由&lt;/h4&gt; &lt;p&gt;查询路由在处理多个索引时非常有用，可将查询定向到最相关的索引以实现高效检索。此方法可确保每个查询都定向到适当的索引，从而简化搜索过程，从而优化信息检索的准确性和速度。&lt;/p&gt; &lt;p&gt;在企业搜索中，数据来自各种来源（例如技术文档、产品文档、任务和代码存储库），查询路由成为一种强大的工具。例如，如果用户正在搜索与特定产品功能相关的信息，则可以智能地将查询路由到包含产品文档的索引，从而提高搜索结果的准确性。&lt;/p&gt; &lt;h4&gt;重排器&lt;/h4&gt; &lt;p&gt;当编码器的检索结果无法提供最佳质量时，可以使用&lt;a href="https://medium.com/@abul.aala.fareh/different-reranking-techniques-in-llamaindex-6a56ed1f30a3" target="_blank"&gt;重新排序器&lt;/a&gt;来提高文档排名。在跨编码器设置中使用开源编码器专用转换器（如&lt;a href="https://huggingface.co/BAAI/bge-large-en-v1.5" target="_blank"&gt;BGE-large）&lt;/a&gt;已成为一种常见做法。最近的解码器专用方法（如&lt;a href="https://arxiv.org/abs/2309.15088" target="_blank"&gt;RankVicuna&lt;/a&gt;、&lt;a href="https://arxiv.org/abs/2304.09542" target="_blank"&gt;RankGPT&lt;/a&gt;和&lt;a href="https://arxiv.org/abs/2312.02724" target="_blank"&gt;RankZephyr）&lt;/a&gt;进一步提高了重新排序器的性能。&lt;/p&gt; &lt;p&gt;引入重排序器有好处，可以减少响应中的&lt;a href="https://www.rungalileo.io/blog/deep-dive-into-llm-hallucinations-across-generative-tasks" target="_blank"&gt;LLM 幻觉&lt;/a&gt;并提高系统的域外泛化能力。但是，它也有缺点。复杂的重排序器可能会因计算开销而增加延迟，从而影响实时应用程序。此外，部署高级重排序器可能会占用大量资源，需要仔细考虑性能提升和资源利用率之间的平衡。&lt;/p&gt; &lt;h4&gt;最大边际相关性（MMR）&lt;/h4&gt; &lt;p&gt;MMR 是一种旨在增强响应查询时检索到的项目多样性、避免冗余的方法。MMR 并非只专注于检索最相关的项目，而是在相关性和多样性之间取得平衡。这就像在聚会上向人们介绍朋友。最初，它会根据朋友的喜好确定最匹配的人。然后，它会寻找略有不同的人。这个过程一直持续到达到所需的介绍次数。MMR 确保呈现一组更加多样化和相关的项目，从而最大限度地减少冗余。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/c0473054513fa046750fbbfc4f08f3ba82624b1f-1282x752.png" alt="原始 MMR" title="原始 MMR" /&gt;&lt;/p&gt; &lt;p&gt;原始 MMR&lt;/p&gt; &lt;h4&gt;自动切割&lt;/h4&gt; &lt;p&gt;&lt;a href="https://weaviate.io/developers/weaviate/api/graphql/additional-operators#autocut" target="_blank"&gt;Weaviate&lt;/a&gt;的自动剪切功能旨在通过检测得分接近的对象组来限制返回的搜索结果数量。它的工作原理是分析搜索结果的得分并识别这些值的显著跳跃，这可能表明从高度相关的结果过渡到不太相关的结果。&lt;/p&gt; &lt;p&gt;例如，考虑返回具有以下距离值的对象的搜索：&lt;/p&gt; &lt;p&gt;[0.1899、0.1901、0.191、0.21、0.215、0.23]。&lt;/p&gt; &lt;p&gt;Autocut 返回以下内容：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;自动切割：1：[0.1899，0.1901，0.191]&lt;/li&gt; &lt;li&gt;自动切割：2：[0.1899, 0.1901, 0.191, 0.21, 0.215]&lt;/li&gt; &lt;li&gt;自动切割：3：[0.1899, 0.1901, 0.191, 0.21, 0.215, 0.23]&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;递归检索&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/6377b36750c946ae2ba7c75ec47fca036324a1ec-2054x800.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;来源：&lt;a href="https://youtu.be/TRjq7t2Ms5I?si=D0z5sHKW4SMqMgSG&amp;amp;t=742" target="_blank"&gt;https://youtu.be/TRjq7t2Ms5I?si= D0z5sHKW4SMqMgSG&amp;amp;t=742&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://docs.llamaindex.ai/en/stable/examples/query_engine/pdf_tables/recursive_retriever.html" target="_blank"&gt;递归检索&lt;/a&gt;，又称从小到大的检索技术，嵌入较小的块进行检索，同时返回较大的父上下文以供语言模型合成。较小的文本块有助于更准确的检索，而较大的块则为语言模型提供更丰富的上下文信息。这种顺序过程通过最初关注较小、信息更密集的单元来优化检索的准确性，然后有效地将其链接到更广泛的上下文父块进行合成。&lt;/p&gt; &lt;h4&gt;句子窗口检索&lt;/h4&gt; &lt;p&gt;检索过程会获取单个句子并返回围绕该特定句子的文本窗口。&lt;a href="https://docs.llamaindex.ai/en/latest/examples/node_postprocessor/MetadataReplacementDemo.html" target="_blank"&gt;句子窗口检索&lt;/a&gt;可确保检索到的信息不仅准确而且与上下文相关，从而提供有关主要句子的全面信息。&lt;/p&gt; &lt;h3&gt;发电机&lt;/h3&gt; &lt;p&gt;既然我们已经讨论了所有检索组件，现在让我们来谈谈生成器。它需要仔细考虑和权衡，主要是在自托管推理部署和私有 API 服务之间。这本身就是一个很大的话题，我将简要介绍一下，以免让您不知所措。&lt;/p&gt; &lt;h4&gt;API 注意事项&lt;/h4&gt; &lt;p&gt;在评估 LLM 的 API 服务器时，确定确保无缝集成和强大性能的功能的优先级至关重要。精心设计的 API 应充当流行 LLM 的简单启动器，同时还要解决生产就绪性、安全性和&lt;a href="https://www.rungalileo.io/blog/5-techniques-for-detecting-llm-hallucinations" target="_blank"&gt;幻觉检测&lt;/a&gt;等关键问题。值得注意的是，&lt;a href="https://github.com/huggingface/text-generation-inference" target="_blank"&gt;HuggingFace 的 TGI 服务器&lt;/a&gt;体现了体现这些原则的一套全面功能。让我们了解 LLM 服务器中所需的一些最受欢迎的功能。&lt;/p&gt; &lt;p&gt;表现&lt;/p&gt; &lt;p&gt;高效的 API 必须优先考虑性能，以满足不同的用户需求。张量并行性是一项突出的功能，它有助于在多个 GPU 上更快地进行推理，从而提高整体处理速度。此外，连续批处理传入请求可确保增加总吞吐量，从而有助于提高系统的响应速度和可扩展性。量化的加入，特别是使用 bitsandbytes 和 GPT-Q，进一步优化了 API，以提高各种用例的效率。利用优化的 Transformer 代码的能力可确保在最流行的架构上进行无缝推理。&lt;/p&gt; &lt;p&gt;生成质量增强剂&lt;/p&gt; &lt;p&gt;为了提高生成质量，API 应包含可以转换输出的功能。logits 处理器包括温度缩放、top-p、top-k 和重复惩罚，允许用户根据自己的偏好自定义输出。此外，停止序列可以控制生成，使用户能够管理和优化内容生成过程。对数概率对于幻觉检测至关重要，它充当了额外的细化层，确保生成的输出与预期上下文一致并避免误导性信息。&lt;/p&gt; &lt;p&gt;安全&lt;/p&gt; &lt;p&gt;API 的安全性至关重要，尤其是在处理 LLM 和企业用例时。Safetensor 权重加载是一项基本功能，它有助于通过防止未经授权篡改模型参数来安全部署模型。此外，水印的加入增加了一层额外的安全性，使 LLM 的使用具有可追溯性和可问责性。&lt;/p&gt; &lt;p&gt;用户体验&lt;/p&gt; &lt;p&gt;在用户体验领域，令牌流成为无缝交互的关键功能。利用服务器发送事件 (SSE) 进行令牌流可增强 API 的实时响应能力，为用户提供更流畅、更具交互性的体验。这可确保用户可以逐步接收生成的内容，从而提高 LLM 的整体参与度和可用性。&lt;/p&gt; &lt;h4&gt;自托管推理&lt;/h4&gt; &lt;p&gt;自托管推理涉及在 AWS、GCP 或 Azure 等云服务提供商提供的服务器上部署 LLM。服务器（例如 TGI、Ray 或 FastAPI）的选择是一项关键决策，直接影响系统的性能和成本。考虑因素包括计算效率、部署的简易性以及与所选 LLM 的兼容性。&lt;/p&gt; &lt;p&gt;衡量 LLM 推理性能至关重要，而像&lt;a href="https://github.com/ray-project/llmperf-leaderboard" target="_blank"&gt;Anyscale 的 LLMPerf Leaderboard&lt;/a&gt;这样的排行榜则非常有价值。它根据关键性能指标对推理提供商进行排名，包括首次标记时间 (TTFT)、标记间延迟 (ITL) 和成功率。负载测试和&lt;a href="https://www.rungalileo.io/blog/mastering-rag-8-scenarios-to-test-before-going-to-production" target="_blank"&gt;正确性测试&lt;/a&gt;对于评估托管模型的不同特征至关重要。&lt;/p&gt; &lt;p&gt;在新方法中，&lt;a href="https://predibase.com/blog/lorax-the-open-source-framework-for-serving-100s-of-fine-tuned-llms-in" target="_blank"&gt;Predibase 的 LoRAX&lt;/a&gt;引入了一种高效服务微调 LLM 的创新方法。它解决了使用共享 GPU 资源服务多个微调模型的挑战。&lt;/p&gt; &lt;h4&gt;私有API服务&lt;/h4&gt; &lt;p&gt;OpenAI、Fireworks、Anyscale、Replicate、Mistral、Perplexity 和 Together 等公司提供的 LLM API 服务提供了替代部署方法。了解它们的功能、定价模型和&lt;a href="https://www.rungalileo.io/blog/metrics-first-approach-to-llm-evaluation" target="_blank"&gt;LLM 性能指标&lt;/a&gt;至关重要。例如，OpenAI 的基于代币的定价（区分输入和输出代币）会显著影响使用 API 的总体成本。在&lt;a href="https://www.rungalileo.io/blog/mastering-rag-improve-performance-with-4-powerful-metrics" target="_blank"&gt;比较私有 API 服务与自托管 LLM 的成本&lt;/a&gt;时，您必须考虑 GPU 成本、利用率和可扩展性问题等因素。对于某些人来说，速率限制可能是一个限制因素（哈哈！）。&lt;/p&gt; &lt;h3&gt;改善 RAG 的提示技术&lt;/h3&gt; &lt;p&gt;有几种提示技术可以提高 RAG 的输出。在&lt;a href="https://www.rungalileo.io/blog/mastering-rag-llm-prompting-techniques-for-reducing-hallucinations" target="_blank"&gt;我们的“掌握 RAG”系列第 2 部分&lt;/a&gt;中，我们深入探讨了最有效的 5 种提示技术。其中许多新技术的表现都超过了 CoT（思维链）。您还可以将它们结合起来以最大限度地减少幻觉。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/df2ad02b9ef391d29be5ca1335b96790be8543c3-3290x1718.png" alt="LLM 针对 RAG 的提示技巧" title="LLM 针对 RAG 的提示技巧" /&gt;&lt;/p&gt; &lt;p&gt;LLM 针对 RAG 的提示技巧&lt;/p&gt; &lt;h3&gt;输出护栏&lt;/h3&gt; &lt;p&gt;输出护栏的功能与输入护栏类似，但专门用于检测生成的输出中的问题。作为&lt;a href="https://www.rungalileo.io/blog/mastering-rag-8-scenarios-to-test-before-going-to-production" target="_blank"&gt;RAG 评估的&lt;/a&gt;一部分，它专注于识别幻觉、竞争对手提及和潜在的品牌损害。目标是防止生成可能与品牌价值观不符的不准确或道德上有问题的信息。通过积极监控和分析输出，此护栏可确保生成的内容在事实上保持准确、符合道德规范并符合品牌的指导方针。&lt;/p&gt; &lt;p&gt;以下是一个可能损害企业品牌但会被适当的输出护栏阻止的响应示例：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/09/02/32c303c1da8f9feb1af5933d415944ea52bb88aa-1678x869.png" alt="有毒输出示例" title="有毒输出示例" /&gt;&lt;/p&gt; &lt;p&gt;有毒输出示例&lt;/p&gt; &lt;h3&gt;用户反馈&lt;/h3&gt; &lt;p&gt;一旦生成并提供了输出，从用户那里获得正面或负面的反馈都是很有帮助的。用户反馈对于改进 RAG 系统的飞轮非常有帮助，这是一个持续的过程，而不是一次性的努力。这不仅需要定期执行重新索引和重新运行实验等自动化任务，还需要采用系统方法来整合用户见解，从而实现实质性的系统增强。&lt;/p&gt; &lt;p&gt;系统改进最有效的杠杆在于主动修复底层数据中的问题。RAG 系统应包含一个迭代工作流程，用于处理用户反馈并推动持续改进。&lt;/p&gt; &lt;h4&gt;用户互动及反馈收集&lt;/h4&gt; &lt;p&gt;用户与 RAG 应用程序交互，并利用 👍/ 👎 或星级评分等功能提供反馈。这套多样化的反馈机制是用户体验和对系统性能的看法的宝贵资源库。&lt;/p&gt; &lt;h4&gt;问题识别和诊断检查&lt;/h4&gt; &lt;p&gt;收集反馈后，团队可以进行全面分析，以确定可能表现不佳的查询。这涉及检查检索到的资源并仔细检查，以辨别表现不佳是源于检索、生成还是底层数据源。&lt;/p&gt; &lt;h4&gt;数据改进策略&lt;/h4&gt; &lt;p&gt;一旦发现问题，尤其是那些根源于数据本身的问题，团队就可以制定战略计划来提高数据质量。这可能涉及纠正不完整的信息或重组组织混乱的内容。&lt;/p&gt; &lt;h4&gt;评估和测试协议&lt;/h4&gt; &lt;p&gt;实施数据改进后，系统必须对之前表现不佳的查询进行&lt;a href="https://www.rungalileo.io/blog/mastering-rag-8-scenarios-to-test-before-going-to-production" target="_blank"&gt;严格评估&lt;/a&gt;。然后，可以从这些评估中获得的见解有条不紊地集成到测试套件中，确保根据实际交互进行持续审查和改进。&lt;/p&gt; &lt;p&gt;通过积极让用户参与这个全面的反馈循环，RAG 系统不仅解决了通过自动化流程发现的问题，而且还利用了丰富的用户体验。&lt;/p&gt; &lt;h3&gt;可观察性&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.rungalileo.io/blog/optimizing-llm-performance-rag-vs-finetune-vs-both" target="_blank"&gt;构建 RAG 系统并不会随着系统投入生产而结束。即使拥有强大的护栏和用于微调&lt;/a&gt;的高质量数据，模型在投入生产后仍需要持续监控。除了延迟和成本等标准指标外，生成式 AI 应用程序还需要特定的&lt;a href="https://docs.rungalileo.io/galileo/llm-studio/llm-monitor" target="_blank"&gt;LLM 可观察性&lt;/a&gt;来检测和纠正幻觉、域外查询和链故障等问题。现在让我们来看看 LLM 可观察性的支柱。&lt;/p&gt; &lt;h4&gt;及时分析和优化&lt;/h4&gt; &lt;p&gt;识别与提示相关的问题，并使用实时生产数据进行迭代，以使用强大的评估机制来识别和解决幻觉等问题。&lt;/p&gt; &lt;h4&gt;LLM 应用程序中的可追溯性&lt;/h4&gt; &lt;p&gt;从常见框架（如 Langchain 和 LlamaIndex）捕获 LLM 跟踪以调试提示和步骤。&lt;/p&gt; &lt;h4&gt;信息检索增强&lt;/h4&gt; &lt;p&gt;排除故障并评估 RAG 参数以优化对 LLM 性能至关重要的检索过程。&lt;/p&gt; &lt;h4&gt;警报&lt;/h4&gt; &lt;p&gt;如果系统行为与预期不符，例如错误增加、高延迟和幻觉，则会收到警报。&lt;/p&gt; &lt;p&gt;首先，实时监控对于观察生产环境中应用程序的性能、行为和整体运行状况至关重要。密切关注 SLA 合规性并设置警报以及时解决任何偏差。通过分析使用模式和资源消耗，有效跟踪与运行 LLM 应用程序相关的成本，以帮助您优化成本。&lt;/p&gt; &lt;p&gt;Galileo 的&lt;a href="https://docs.rungalileo.io/galileo/#llm-studio" target="_blank"&gt;LLM Studio&lt;/a&gt;提供专门构建的 LLM 可观察性，可在用户投诉之前主动发出警报并立即采取纠正措施。Galileo 的护栏指标旨在监控模型的质量和安全性，涵盖基础性、不确定性、事实性、语气、毒性、PII 等方面。这些指标以前在&lt;a href="https://www.rungalileo.io/blog/mastering-rag-8-scenarios-to-test-before-going-to-production" target="_blank"&gt;评估&lt;/a&gt;和&lt;a href="https://www.rungalileo.io/blog/mastering-rag-improve-performance-with-4-powerful-metrics" target="_blank"&gt;实验&lt;/a&gt;期间使用，现在可以无缝集成到监控阶段。&lt;/p&gt; &lt;p&gt;此外，您还可以灵活地注册自定义指标，以根据您的特定需求定制监控流程。利用监控数据生成的见解和警报，随时了解潜在问题、异常或需要注意的改进领域。这种全面的方法可确保您的 LLM 应用程序在实际场景中高效、安全地运行。&lt;/p&gt; &lt;h3&gt;缓存&lt;/h3&gt; &lt;p&gt;对于规模化运营的公司来说，成本可能成为一大障碍。在这种情况下，缓存是一种很好的省钱方法。缓存涉及将提示及其相应的响应存储在数据库中，以便随后检索它们以供使用。这种战略性缓存机制使 LLM 应用程序能够加快和节省响应，具有三个明显的优势。&lt;/p&gt; &lt;h4&gt;增强生产推理&lt;/h4&gt; &lt;p&gt;缓存有助于在生产过程中实现更快、更经济的推理。某些查询可以通过利用缓存响应实现接近零延迟，从而简化用户体验。&lt;/p&gt; &lt;h4&gt;加速开发周期&lt;/h4&gt; &lt;p&gt;在开发阶段，缓存被证明是一种福音，因为它消除了重复调用相同提示的 API 的需要。这可以缩短开发周期，并使其更经济。&lt;/p&gt; &lt;h4&gt;数据存储&lt;/h4&gt; &lt;p&gt;存储所有提示的综合数据库简化了 LLM 的微调过程。利用存储的提示-响应对简化了基于累积数据的模型优化。&lt;/p&gt; &lt;p&gt;如果您认真考虑，可以利用&lt;a href="https://github.com/zilliztech/GPTCache" target="_blank"&gt;GPTCache&lt;/a&gt;来实现精确匹配和相似匹配的缓存。它提供了缓存命中率、延迟和召回率等有价值的指标，这些指标可以洞悉缓存的性能，从而实现持续改进以确保最佳效率。&lt;/p&gt; &lt;h3&gt;多租户&lt;/h3&gt; &lt;p&gt;SaaS 软件通常有多个租户，以平衡简单性和隐私性。对于 RAG 系统中的多租户，目标是构建一个不仅能有效查找信息而且尊重每个用户数据限制的系统。简而言之，每个用户与系统的交互都是独立的，确保系统只查看和使用针对该用户的信息。&lt;/p&gt; &lt;p&gt;构建多租户的简单方法之一是使用元数据。当我们将文档添加到系统时，我们会在元数据中包含特定用户的详细信息。这样，每个文档都与特定用户相关联。当有人搜索时，系统会使用此元数据进行筛选，仅显示与该用户相关的文档。然后，它会进行智能搜索，以找到对该用户最重要的信息。这种方法可以防止私人信息在不同用户之间混淆，从而确保每个人的数据安全且私密。&lt;/p&gt; &lt;p&gt;了解如何&lt;a href="https://blog.llamaindex.ai/building-multi-tenancy-rag-system-with-llamaindex-0d6ab4e0c44b" target="_blank"&gt;使用 Llamaindex 实现多租户&lt;/a&gt;。&lt;/p&gt; &lt;h2&gt;结论&lt;/h2&gt; &lt;p&gt;显然，构建一个强大且可扩展的企业 RAG 系统需要精心协调相互关联的组件。从用户身份验证到输入护栏、查询重写、编码、文档提取以及矢量数据库和生成器等检索组件，每一步都对系统性能的塑造起着至关重要的作用。&lt;/p&gt; &lt;p&gt;在不断发展的 RAG 系统格局中，我们希望本实用指南能够为开发人员和领导者提供可行的见解！&lt;/p&gt; &lt;p&gt;Galileo GenAI Studio 是面向构建 LLM 驱动应用程序的团队的领先平台，可进行快速评估、实验和&lt;a href="https://docs.rungalileo.io/galileo/llm-studio/llm-monitor" target="_blank"&gt;可观察性。它由&lt;/a&gt;&lt;a href="https://docs.rungalileo.io/galileo/llm-studio/prompt-inspector/choosing-your-guardrail-metrics" target="_blank"&gt;一套指标&lt;/a&gt;提供支持，用于识别和缓解幻觉。加入 1000 多名构建 LLM 驱动应用程序的开发人员，并&lt;a href="https://www.rungalileo.io/llm-studio/#join-waitlist" target="_blank"&gt;获得早期访问权限&lt;/a&gt;！&lt;/p&gt; &lt;p&gt;转载于:&lt;a href="https://www.rungalileo.io/blog/mastering-rag-how-to-architect-an-enterprise-rag-system" target="_blank"&gt;https://www.rungalileo.io/blog/mastering-rag-how-to-architect-an-enterprise-rag-system&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 05 Sep 2024 01:47:00 GMT</pubDate>
    </item>
    <item>
      <title>Centos python2.7升级到python3</title>
      <link>https://maruifu.cn/article/315</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;#!/bin/bash set -e  # 指定要安装的Python版本 PYTHON_VERSION=&amp;quot;3.10.12&amp;quot;  echo &amp;quot;=== 安装编译依赖 ===&amp;quot; # 安装编译依赖 sudo yum install -y gcc gcc-c++ zlib zlib-devel readline-devel  echo &amp;quot;=== 下载 Python 源码包 ===&amp;quot; # 下载Python源码包（从华为镜像源下载） wget https://mirrors.huaweicloud.com/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz  echo &amp;quot;=== 解压 Python 源码包 ===&amp;quot; # 解压Python源码包 tar xvf Python-$PYTHON_VERSION.tar.xz  echo &amp;quot;=== 进入 Python 源码目录 ===&amp;quot; # 进入Python源码目录 cd Python-$PYTHON_VERSION  echo &amp;quot;=== 配置并编译 Python ===&amp;quot; # 编译并安装Python ./configure make &amp;amp;&amp;amp; sudo make install  echo &amp;quot;=== 更改默认 python 链接 ===&amp;quot; # 更改默认python链接 sudo mv /usr/bin/python /usr/bin/python.bak sudo ln -s /usr/local/bin/python3 /usr/bin/python  echo &amp;quot;=== 配置 yum 脚本和 urlgrabber-ext-down 脚本 ===&amp;quot; # 配置yum脚本和urlgrabber-ext-down脚本 sudo sed -i &amp;quot;s|#!/usr/bin/python|#!/usr/bin/python2.7|&amp;quot; /usr/bin/yum sudo sed -i &amp;quot;s|#!/usr/bin/python|#!/usr/bin/python2.7|&amp;quot; /usr/libexec/urlgrabber-ext-down  echo &amp;quot;=== 升级 Python 3 完成！ ===&amp;quot;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我的博客即将同步至腾讯云开发者社区，邀请大家一同入驻：https://cloud.tencent.com/developer/support-plan?invite_code=182do5sqcmvp7&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 13 Aug 2024 02:30:00 GMT</pubDate>
    </item>
    <item>
      <title>CentOS采用PPPOE拨号上网</title>
      <link>https://maruifu.cn/article/314</link>
      <content:encoded>&lt;h3&gt;安装拨号软件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;yum install rp-pppoe -y &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;使用pppoe-setup进行交互式配置&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; pppoe-setup &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;[root@192 ~]# pppoe-setup Welcome to the PPPoE client setup.  First, I will run some checks on your system to make sure the PPPoE client is installed properly...   LOGIN NAME  Enter your Login Name (default root):  #输入ISP提供的账户（上网帐号，这个是联通公司给我的那个帐号）  INTERFACE  Enter the Ethernet interface connected to the PPPoE modem For Solaris, this is likely to be something like /dev/hme0. For Linux, it will be ethX, where 'X' is a number. (default eth0):  #输入以太网卡代号，默认是eth0(自行使用ifconfig命令即可找到)  Do you want the link to come up on demand, or stay up continuously? If you want it to come up on demand, enter the idle time in seconds after which the link should be dropped.  If you want the link to stay up permanently, enter 'no' (two letters, lower-case.) NOTE: Demand-activated links do not interact well with dynamic IP addresses.  You may have some problems with demand-activated links. Enter the demand value (default no):  #配置：若长时间连线，连线会被自动中断（选no表示：即使没有数据连接时也不允许中断） DNS  Please enter the IP address of your ISP's primary DNS server. If your ISP claims that 'the server will provide dynamic DNS addresses', enter 'server' (all lower-case) here. If you just press enter, I will assume you know what you are doing and not modify your DNS setup. Enter the DNS information here: 114.114.114.114 #配置主DNS服务器（我不知道我们网络服务商的DNS，所以填写的是通用地址：114.114.114.114） Please enter the IP address of your ISP's secondary DNS server. If you just press enter, I will assume there is only one DNS server. Enter the secondary DNS server address here: 8.8.8.8 #配置次DNS服务器（可以空着直接敲回车）  PASSWORD # 两次输入账户密码以确认（上网帐号对应的密码--也是联通公司给我的那个，要输入两次，linux下键入密码时不会显示东西） Please enter your Password:  Please re-enter your Password:   USERCTRL #配置普通账户是否有网络连接权限（这里应该输入yes） Please enter 'yes' (three letters, lower-case.) if you want to allow normal user to start or stop DSL connection (default yes):   FIREWALLING  Please choose the firewall rules to use.  Note that these rules are very basic.  You are strongly encouraged to use a more sophisticated firewall setup; however, these will provide basic security.  If you are running any servers on your machine, you must choose 'NONE' and set up firewalling yourself.  Otherwise, the firewall rules will deny access to all standard servers like Web, e-mail, ftp, etc.  If you are using SSH, the rules will block outgoing SSH connections which allocate a privileged source port.  The firewall choices are: 0 - NONE: This script will not set any firewall rules.  You are responsible           for ensuring the security of your machine.  You are STRONGLY           recommended to use some kind of firewall rules. 1 - STANDALONE: Appropriate for a basic stand-alone web-surfing workstation 2 - MASQUERADE: Appropriate for a machine acting as an Internet gateway                 for a LAN Choose a type of firewall (0-2): 0 # 配置防火墙（没有特殊需求选0就OK） Start this connection at boot time  Do you want to start this connection at boot time? Please enter no or yes (default no):yes #配置是否开机自动拨号连接（我选择的是yes） ** Summary of what you entered **  Ethernet Interface: eth0 User name:          990001230447 Activate-on-demand: No Primary DNS:        114.114.114.114 Secondary DNS:      8.8.8.8 Firewalling:        NONE User Control:       yes Accept these settings and adjust configuration files (y/n)? y # 确认刚填写的配置信息（没有问题的话，就选择y） Adjusting /etc/sysconfig/network-scripts/ifcfg-ppp0 Adjusting /etc/resolv.conf   (But first backing it up to /etc/resolv.conf.bak) Adjusting /etc/ppp/chap-secrets and /etc/ppp/pap-secrets   (But first backing it up to /etc/ppp/chap-secrets.bak)   (But first backing it up to /etc/ppp/pap-secrets.bak)    Congratulations, it should be all set up!  Type '/sbin/ifup ppp0' to bring up your xDSL link and '/sbin/ifdown ppp0' to bring it down. Type '/sbin/pppoe-status /etc/sysconfig/network-scripts/ifcfg-ppp0' to see the link status.  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看状态&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;pppoe-status &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;启动&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;pppoe-start &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;连接网络&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;/sbin/ifup ppp0 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;断开连接&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;/sbin/ifdown ppp0 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 08 Aug 2024 07:33:00 GMT</pubDate>
    </item>
    <item>
      <title>修改CentOS默认yum源为国内yum镜像源</title>
      <link>https://maruifu.cn/article/312</link>
      <content:encoded>&lt;p&gt;1、首先备份系统自带yum源配置文件/etc/yum.repos.d/CentOS-Base.repo&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2、下载ailiyun的yum源配置文件到/etc/yum.repos.d/&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3、更新配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# yum clean all [root@localhost ~]# yum makecache [root@localhost ~] # yum -y update &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 08 Aug 2024 06:59:00 GMT</pubDate>
    </item>
    <item>
      <title>使用指定的日期的快照版本</title>
      <link>https://maruifu.cn/article/311</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;发现官方更新了快照,本地也强制更新了,更新后无法使用&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;我新下载的是 spring-ai-ollama-1.0.0-20240805.173614-463.jar 更新后报错&lt;/p&gt; &lt;p&gt;,但是我使用旧版本spring-ai-ollama-1.0.0-20240725.032054-428.jar 没有问题&lt;/p&gt; &lt;p&gt;本地install 最新的版本&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mvn install:install-file -Dfile=/Users/maruifu/.m2/repository/org/springframework/ai/spring-ai-ollama/1.0.0-SNAPSHOT/spring-ai-ollama-1.0.0-20240725.032054-428.jar \                          -DgroupId=org.springframework.ai \                          -DartifactId=spring-ai-ollama-spring-boot-starter \                          -Dversion=1.0.0-20240725.032054-428 \                          -Dpackaging=jar &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 06 Aug 2024 08:31:01 GMT</pubDate>
    </item>
    <item>
      <title>加载.mjs文件报错 MIME type of "application/octet-stream" 的解决方案</title>
      <link>https://maruifu.cn/article/310</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;关于浏览器控制台报错 xxx.mjs 文件 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of &amp;quot;application/octet-stream&amp;quot;. Strict MIME type checking is enforced for module scripts per HTML spec.问题的解决方案&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;第一次遇到这个问题，本地访问没问题，使用Nginx部署好后，但浏览器控制台报错提示： .mjs Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of &amp;quot;application/octet-stream&amp;quot;. Strict MIME type checking is enforced for module scripts per HTML spec.&lt;/p&gt; &lt;h3&gt;原因&lt;/h3&gt; &lt;p&gt;由于nginx无法识别mjs文件，从而在http header中错误的使用 Content-Type:application/octet-stream 来传输mjs文件，导致浏览器端认为它不是一个合法的js脚本&lt;/p&gt; &lt;h3&gt;解决方案&lt;/h3&gt; &lt;p&gt;修改nginx的MIME type文件，修改对应的MIME type与mjs的映射，操作如下：&lt;/p&gt; &lt;p&gt;去nginx配置文件中发现 mimetype文件路径为 /etc/nginx/mime.types&lt;/p&gt; &lt;p&gt;打开mime.types文件&lt;/p&gt; &lt;pre&gt;&lt;code class="language-awk"&gt;sudo vim /etc/nginx/mime.types &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;找到如下配置内容&lt;/p&gt; &lt;pre&gt;&lt;code class="language-abnf"&gt;application/javascript                 js; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改为&lt;/p&gt; &lt;pre&gt;&lt;code class="language-abnf"&gt;application/javascript                 js mjs; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后重启Nginx&lt;/p&gt; &lt;pre&gt;&lt;code class="language-ebnf"&gt;sudo nginx -s reload &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再次访问发现网页没有报错，可以正常运行（如还有报错可尝试强刷或清除缓存后再试）&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 26 Jul 2024 07:50:00 GMT</pubDate>
    </item>
    <item>
      <title>解决knife4j多文件上传问题</title>
      <link>https://maruifu.cn/article/309</link>
      <content:encoded>&lt;h2&gt;问题&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;多文件上传knife4j文档接口不显示文件选择按钮&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;请求接口代码&lt;/p&gt; &lt;p&gt;@ApiOperation(&amp;quot;多文件上传&amp;quot;) @PostMapping(&amp;quot;/uploads&amp;quot;) public AjaxResult uploadFiles(@RequestPart List&lt;MultipartFile&gt; files){&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这样设置后后无法选择文件&lt;/p&gt; &lt;h2&gt;解决&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;在后端请求接口上添加&lt;code&gt;@ApiImplicitParam&lt;/code&gt;指定请求参数类型&lt;/li&gt; &lt;li&gt;使用dataType&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;@ApiImplicitParam(name = &amp;quot;files&amp;quot;, value = &amp;quot;上传的文件&amp;quot;, dataType = &amp;quot;java.io.File&amp;quot;, required = true) # 或者使用dataTypeClass @ApiImplicitParam(name = &amp;quot;files&amp;quot;, value = &amp;quot;上传的文件&amp;quot;, dataTypeClass = MultipartFile.class, required = true)  public AjaxResult uploadFiles(@RequestPart List&amp;lt;MultipartFile&amp;gt; files){ &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;但是存在问题，只能支持单选文件，不支持多选&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;文件多选问题&lt;/h2&gt; &lt;p&gt;1.添加全局参数  在knife4j全局参数配置里面添加&lt;/p&gt; &lt;p&gt;2.使用knife4j增强功能，开启动态请求&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 12 Jul 2024 08:05:23 GMT</pubDate>
    </item>
    <item>
      <title>PGvector Docker 容器无法启动！</title>
      <link>https://maruifu.cn/article/308</link>
      <content:encoded>&lt;h2&gt;用docker安装pgvector&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;docker run --name pgvector \     -e POSTGRES_PASSWORD=postgres \     -p 5432:5432 \  -d pgvector/pgvector:pg16 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;报错&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;initdb: error: program &amp;quot;postgres&amp;quot; is needed by initdb but was not found in the same directory as &amp;quot;/usr/lib/postgresql/16/bin/initdb&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;增加 privileged=true 可以解决 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 08 Jul 2024 01:58:38 GMT</pubDate>
    </item>
    <item>
      <title>Linux内网离线安装Ollama</title>
      <link>https://maruifu.cn/article/307</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;我下载的是Ollama的 0.1.49 版本，后面均以此版本为例说明&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;在线安装&lt;/h2&gt; &lt;p&gt;在线安装地址https://ollama.com/download选择服务器系统，按照步骤完成安装&lt;/p&gt; &lt;h2&gt;离线安装&lt;/h2&gt; &lt;h3&gt;查看服务器CPU的型号&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;## 查看Linux系统CPU型号命令，我的服务器cpu型号是x86_64 lscpu &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/07/08/image-20240708085418130.png" alt="image-20240708085418130" title="image-20240708085418130" /&gt;&lt;/p&gt; &lt;h3&gt;根据CPU型号下载Ollama安装包&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;保存到&lt;code&gt;/home/Ollama&lt;/code&gt;目录&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;下载地址 https://github.com/ollama/ollama/releases/&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# x86_64 CPU选择下载ollama-linux-amd64 # aarch64|arm64 CPU选择下载ollama-linux-arm64 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/07/08/image-20240708090026111.png" alt="image-20240708090026111" title="image-20240708090026111" /&gt;&lt;/p&gt; &lt;h3&gt;离线下载Linux环境的Ollama安装脚本&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;保存到/home/Ollama目录&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;## 下载地址1，浏览器中打开下面地址 https://ollama.com/install.sh ## 下载地址2 https://github.com/ollama/ollama/blob/main/scripts/install.sh &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改install.sh脚本&lt;/h3&gt; &lt;p&gt;总共需要修改两个点  Ollama下载地址和 Ollama安装包存放目录&lt;/p&gt; &lt;h4&gt;注释下载链接&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;status &amp;quot;Downloading ollama...&amp;quot; ## 在install.sh的第65行 #curl --fail --show-error --location --progress-bar -o $TEMP_DIR/ollama &amp;quot;https://ollama.com/download/ollama-linux-${ARCH}${VER_PARAM}&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;修改ollama安装目录&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;status &amp;quot;Installing ollama to $BINDIR...&amp;quot; $SUDO install -o0 -g0 -m755 -d $BINDIR ## 在install.sh的第73行 #$SUDO install -o0 -g0 -m755 $TEMP_DIR/ollama $BINDIR/ollama $SUDO install -o0 -g0 -m755 ./ollama-linux-amd64  $BINDIR/ollama &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;运行 install.sh脚本安装&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 执行installl.sh脚本，需要sudo 权限  chmod +x install.sh ./install.sh # 如果报错误权限不足，执行 chmod +x install.sh &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;运行大模型，如通义千问&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 需要先将大模型下载到OLLAMA_MODELS文件中 # ollama run &amp;lt;模型名称&amp;gt; ollama run qwen2:7b &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;关闭 Ollama 服务&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 关闭ollama服务 service ollama stop &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Ollama 常用命令&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;## 启动Ollama服务 ollama serve ## 从模型文件创建模型 ollama create ## 显示模型信息 ollama show ## 运行模型 ollama run 模型名称 ## 从注册表中拉去模型 ollama pull 模型名称 ## 将模型推送到注册表 ollama push ## 列出模型 ollama list ## 复制模型 ollama cp ## 删除模型 ollama rm 模型名称 ## 获取有关Ollama任何命令的帮助信息 ollama help &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;FAQ&lt;/h2&gt; &lt;h3&gt;如何查看运行的日志？&lt;/h3&gt; &lt;p&gt;在Linux上运行命令&lt;code&gt;journalctl -u ollama&lt;/code&gt;，即可查看运行日志。&lt;/p&gt; &lt;h3&gt;如何配置本地大模型对局域网提供服务？&lt;/h3&gt; &lt;p&gt;在Linux上修改配置文件，并配置环境变量 &lt;code&gt;OLLAMA_HOST&lt;/code&gt; 来指定对局域网提供服务的地址，再重启Ollama服务即可。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;vim /etc/systemd/system/ollama.service  在[Service]下面加上： Environment=&amp;quot;OLLAMA_HOST=0.0.0.0:11434&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;重启后，就可以通过http在局域网中访问了&lt;/p&gt; &lt;h3&gt;下载的大模型存储在哪个路径？&lt;/h3&gt; &lt;p&gt;默认情况下，不同操作系统存储的路径如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;macOS: ~/.ollama/models Linux: /usr/share/ollama/.ollama/models Windows: C:\Users&amp;lt;username&amp;gt;.ollama\models  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 08 Jul 2024 01:19:54 GMT</pubDate>
    </item>
    <item>
      <title>Java 如何用SSE实现消息推送</title>
      <link>https://maruifu.cn/article/306</link>
      <content:encoded>&lt;h2&gt;定义存储池&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;        /**   * 用于存储SseEmitter对象   */  private static final Map&amp;lt;String, SseEmitter&amp;gt; pool = new ConcurrentHashMap&amp;lt;&amp;gt;();  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;发布消息&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;       /**   * 描述 发布消息   * @param id   * @param message   */  @PostMapping(&amp;quot;/publisher/{id}&amp;quot;)  public void publish(@PathVariable String id, @RequestBody String message) {   SseEmitter sseEmitter = pool.get(id);   if(Objects.nonNull(sseEmitter)){    try {     sseEmitter.send(message);    } catch (Exception e) {     throw  new RuntimeException(e);    }   }  }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;订阅消息&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;       /**   * 描述 订阅消息   * @param id   * @return   */  @GetMapping(&amp;quot;/subscribe/{id}&amp;quot;)  public SseEmitter subscribe(@PathVariable String id) {   SseEmitter sseEmitter = pool.get(id);   if(Objects.isNull(sseEmitter)){    sseEmitter = new SseEmitter();    sseEmitter.onCompletion(() -&amp;gt; pool.remove(id));    sseEmitter.onTimeout(() -&amp;gt; pool.remove(id));    pool.put(id, sseEmitter);   }    return sseEmitter;  } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;测试&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;// 接收消息 curl http://127.0.0.1:8080/subscribe/1  // 发送消息 curl -d &amp;quot;message=1&amp;quot; http://127.0.0.1:8080/publisher/1 curl -d &amp;quot;message=2&amp;quot; http://127.0.0.1:8080/publisher/1 curl -d &amp;quot;message=3&amp;quot; http://127.0.0.1:8080/publisher/1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/06/16/Kapture-2024-06-16-at-02.16.17.gif" alt="测试" title="测试" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 15 Jun 2024 17:16:00 GMT</pubDate>
    </item>
    <item>
      <title>使用tar+pv 命令实现压缩和解压缩显示进度条</title>
      <link>https://maruifu.cn/article/305</link>
      <content:encoded>&lt;p&gt;实现该功能需要安装 pv，然后把需要处理的数据通过管道传给 pv，最后再进行操作。&lt;/p&gt; &lt;p&gt;传给 pv 的目的是为了知道已经处理的数据量大小，同时需要通过 -s 指定总共需要处理的数据量大小。&lt;/p&gt; &lt;p&gt;pv 的安装一般的软件管理工具都有提供：&lt;/p&gt; &lt;h2&gt;安装pv&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;$ sudo yum install pv &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;pv 的详细用法可参考：https://linux.die.net/man/1/pv&lt;/p&gt; &lt;h2&gt;准备测试目录&lt;/h2&gt; &lt;p&gt;准备一个测试压缩的目录test，里面放了连个文本文件1.txt和2.txt&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;[root@localhost tar_pv]# ls test 1.txt  2.txt &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;压缩文件&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;$ tar -cf - test | pv -s $(du -sk test | awk '{print $1}') | gzip &amp;gt; test.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;实际操作&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# tar -cf - test | pv -s $(du -sk test | awk '{print $1}') | gzip &amp;gt; test.tar.gz   10kiB 0:00:00 [ 256MiB/s] [================================================================================================] 85333%             [root@localhost ~]#  [root@localhost ~]# ll total 21648 drwxr-xr-x 2 root root     4096 Nov  4 14:28 test -rw-r--r-- 1 root root      173 Nov  4 15:48 test.tar.gz [root@localhost ~]# tar -tvf test.tar.gz  drwxr-xr-x root/root         0 2021-11-04 14:28 test/ -rw-r--r-- root/root         4 2021-11-04 14:13 test/1.txt -rw-r--r-- root/root         5 2021-11-04 14:14 test/2.txt [root@localhost ~]#  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;命令详解：&lt;/p&gt; &lt;p&gt;tar -cf -f 是指定目标文件， - , 代表将需要压缩的数据输出到 stdout（标准输出），这样管道的后面才可以接收到需要处理的数据。&lt;/p&gt; &lt;p&gt;pv 命令的作用是，将输入复制一遍然后输出到 stdout，这样最后的 gzip 命令才有数据可以处理，最后通过 gzip 命令把前面的输出处理之后保存到 test.tar.gz。&lt;/p&gt; &lt;p&gt;pv -s 后面一串的命令是获取需要处理的所有数据的大小（单位：字节），mac 下 du -sk 获取到的数值是以 k 为单位的，而其他 *nix 系统则不一样，参数需要改为 du -sb，同时也不需要再乘以 1024。&lt;/p&gt; &lt;h2&gt;解压缩&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;$ pv test.tar.gz | tar -zxf - &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;实际操作&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# pv test.tar.gz | tar -zxf -  173 B 0:00:00 [   5MiB/s] [==================================================================================================&amp;gt;] 100%             [root@localhost ~]#  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;命令详解：通过 pv 读取需要解压的文件到 stdout，然后通过管道传递给 tar 命令，tar 命令从标准输入获取要处理的数据进行解压。&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 07 Jun 2024 07:49:59 GMT</pubDate>
    </item>
    <item>
      <title>给tar压缩增加进度条</title>
      <link>https://maruifu.cn/article/304</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;有点花里胡哨，在生产环境中还是建议使用 tar zcvf 这样简单的参数少的命令减少错误产生。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;用pv 和du 显示进度条 需要先安装 pv工具，如ubuntu apt install pv 利用du先获得文件或目录大小，然后输出给 pv显示。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;tar -cf - “要压缩的文件或者目录路径”| pv -s $(($(du -sk “要压缩的文件或者目录路径” | awk '{print $1}') * 1024)) | gzip &amp;gt; {输出的文件名}.tar.gz &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 06 Jun 2024 00:29:00 GMT</pubDate>
    </item>
    <item>
      <title>open-webui镜像启动失败</title>
      <link>https://maruifu.cn/article/303</link>
      <content:encoded>&lt;p&gt;发现镜像启动几秒后就退出了。于是使用 logs命令查看docker镜像启动的日志。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(base) maruifu@maruifudeMBP ~ % docker ps CONTAINER ID   IMAGE                                COMMAND           CREATED         STATUS         PORTS                     NAMES 948e39d916a7   ghcr.io/open-webui/open-webui:main   &amp;quot;bash start.sh&amp;quot;   7 seconds ago   Up 7 seconds   0.0.0.0:11433-&amp;gt;8080/tcp   ollama-webui (base) maruifu@maruifudeMBP ~ % docker logs 948e39d916a7 /usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:160: UserWarning: Field &amp;quot;model_name&amp;quot; has conflict with protected namespace &amp;quot;model_&amp;quot;. ...此处省略不到十万行... Error while downloading from https://cdn-lfs.huggingface.co/sentence-transformers/all-MiniLM-L6-v2/53aa51172d142c89d9012cce15ae4d6cc0ca6895895114379cacb4fab128d9db?response-content-disposition=attachment%3B+filename*%3DUTF- ...此处省略不到n字... OMPD-a1GbAkTR-neqjRjVYVKfO9sbkdU13%7ESK1fbWgSQ__&amp;amp;Key-Pair-Id=KVTP0A1DKRTAX: HTTPSConnectionPool(host='cdn-lfs.huggingface.co', port=443): Read timed out. Trying to resume download...  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;看了日志才知道，原来是open-webui启动时期需要首先在线安装transformers库，但是huggingface.co对于国内来说是经常不可访问):&lt;/p&gt; &lt;p&gt;　　于是赶紧求助bing，找到一个镜像网站，&amp;quot;hf-mirror.com&amp;quot;，于是给docker启动命令中增加一个环境变量&amp;quot;HF_ENDPOINT&amp;quot;，经测试完美解决了。后来又想到开始虽然运行镜像启动成功了，但是启动特别慢，2、3分钟服务才能访问，看来也是需要访问huggingface网站的原因。&lt;/p&gt; &lt;p&gt;　　注意这里设置环境变量HF_ENDPOINT，必须设置为”https://hf-mirror.com“，否则依然会报错，大概是说未知的scheme，我已经趟过这个坑了。完整的命令行如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -d \    --restart unless-stopped \    --name ollama-webui \    -p 11433:8080 \    -v /Users/maruifu/work/ai-code/ollama/data:/app/backend/data \    -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api \    -e HF_ENDPOINT=https://hf-mirror.com \    -e WEBUI_SECRET_KEY=12345678 \    ghcr.io/open-webui/open-webui:main &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 04 May 2024 17:10:38 GMT</pubDate>
    </item>
    <item>
      <title>Mac系统中Anaconda安装配置及Jupyter notebook 配置使用问题</title>
      <link>https://maruifu.cn/article/302</link>
      <content:encoded>&lt;p&gt;要学习Python数据分析，环境搭建是最基础的知识点，目前Anaconda 和Jupyter notebook是数据分析的标准环境。&lt;/p&gt; &lt;p&gt;Anaconda是包管理器和环境管理器，Jupyter notebook可以将数据分析的代码、图像和文档全部组合到一个web文档中。&lt;/p&gt; &lt;h2&gt;安装Anaconda&lt;/h2&gt; &lt;p&gt;1、Anaconda可用于多个平台（Windows、 Mac OS 和Linux），直接去官网地址根据操作系统版本对应下载：&lt;/p&gt; &lt;p&gt;&lt;a href="https://docs.anaconda.com/anaconda/install/" target="_blank"&gt;Anaconda官网下载地址&lt;/a&gt;&lt;/p&gt; &lt;p&gt;此为MacOS版本，下载好软件后一路默认安装。&lt;/p&gt; &lt;p&gt;2、命令行安装方式是打开终端，执行以下命令：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;bash ~/Downloads/Anaconda3-5.3.1-MacOSX-x86_64.sh   //python3版本 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;过程默认安装，可选择自动添加环境变量。看到”Thank you for installing Anaconda!”就安装完成了。&lt;/p&gt; &lt;p&gt;然后你source一下或者重启终端使新加的环境变量生效&lt;/p&gt; &lt;pre&gt;&lt;code&gt;source ~/.bash_profile    //或者是source ~/.zshrc_profile &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意：Mac安装好后的Anaconda可直接在图形界面进行包的管理和操作，有的会出现桌面菜单不现实Anaconda 的情况，打不开可以看我上篇文章，接下来的学习教程都是在终端命令行完成。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;conda操作命令&lt;/h2&gt; &lt;h3&gt;常用命令&lt;/h3&gt; &lt;h4&gt;查看conda 版本&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;可检测安装Anaconda是否成功&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;conda --version   // 或者conda -V &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;更新conda 版本&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda update conda &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查看安装的依赖库&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda list &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查找包&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda search XXX &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;安装包&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda install XXX &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;更新包&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda update XXX &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;删除包&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda remove XXX  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;conda 虚拟环境&lt;/h3&gt; &lt;h4&gt;&lt;strong&gt;anaconda默认自带环境base&lt;/strong&gt;&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;若之前从未进入base环境时，需先进入anaconda自带环境base&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;source /opt/anaconda3/bin/activate base &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查看所有虚拟环境和信息&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;// 有三种命令均可查询 conda info -e conda info --envs conda env list &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;创建新的虚拟环境&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda create --name env_name // 或者是指定python版本 conda create -n env_name python=3.x &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;“env_name”为自己创建的环境名；3.x 根据需求选择版本；&lt;/p&gt; &lt;p&gt;新的虚拟环境会自动创建在原anaconda的envs目录中&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;创建新环境并指定包含的库&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda create -n env_name scipy // 并且可以指定库的版本 conda create -n env_name scipy=0.15.0 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;复制环境&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda create --name env_clone --clone env_name &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;激活进入虚拟环境&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;source activate env_name &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;退出当前虚拟环境&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;source deactivate &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;删除环境&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda remove --name env_name --all &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;查看某个环境下安装的库&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;conda list -n env_name  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;指令进入Jupyter notebook 并配置虚拟环境&lt;/h2&gt; &lt;h3&gt;激活虚拟环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;conda activate env_name &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装jupyter notebook&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;conda install jupyter notebook &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;默认端口启动运行&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;jupyter notebook &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改 Jupyter的python环境&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;ipykernel是一个Python库,它提供了一个用于在Jupyter环境中运行Python代码的内核&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;# 安装ipykernel pip install ipykernel    // 或者是 conda install ipykernel # ipykernel中安装当前环境    python -m ipykernel install --name env_name  # 若报错[Errno 13] Permission denied: python -m ipykernel install --user --name env_name  # 如果在Jupyter中安装包遇到问题：Note: you may need to restart the kernel to use updated packages. 更新完成后restate pip install ipykernel --upgrade &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改JupterNotebook 保存目录&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 找到配置文件 jupyter notebook --generate-config  #找到NotebookApp.notebook_dir  找不到直接 新增  915 # c.ServerApp.notebook_dir = '' 916 c.NotebookApp.notebook_dir = '/Users/maruifu/work/doc/JupterNotebook' &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 17 Apr 2024 03:41:03 GMT</pubDate>
    </item>
    <item>
      <title>Mac下Anaconda Navigator打不开的解决办法</title>
      <link>https://maruifu.cn/article/301</link>
      <content:encoded>&lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;点击 Anaconda-navigator 的图标后，图表会出现几秒(initializing)，然后就退出&lt;/p&gt; &lt;h2&gt;升级版本&lt;/h2&gt; &lt;p&gt;打开终端，输入指令： &lt;code&gt;sudo conda update conda&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt; maruifu@maruifudeMacBook-Pro ~ % sudo conda update conda Password: Channels:  - defaults Platform: osx-64 Collecting package metadata (repodata.json): done Solving environment: done  ## Package Plan ##    environment location: /opt/anaconda3    added / updated specs:     - conda   The following packages will be downloaded:      package                    |            build     ---------------------------|-----------------     archspec-0.2.3             |     pyhd3eb1b0_0          47 KB     ca-certificates-2024.3.11  |       hecd8cb5_0         128 KB     conda-24.3.0               |  py311hecd8cb5_0         1.2 MB     ------------------------------------------------------------                                            Total:         1.4 MB  The following packages will be UPDATED:    archspec                               0.2.1-pyhd3eb1b0_0 --&amp;gt; 0.2.3-pyhd3eb1b0_0    ca-certificates                     2023.12.12-hecd8cb5_0 --&amp;gt; 2024.3.11-hecd8cb5_0    conda                              24.1.2-py311hecd8cb5_0 --&amp;gt; 24.3.0-py311hecd8cb5_0    Proceed ([y]/n)? y   Downloading and Extracting Packages:                                                                                  Preparing transaction: done                                                      Verifying transaction: done                                                      Executing transaction: done  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;等待安装，输入指令&lt;/p&gt; &lt;p&gt;&lt;code&gt;sudo conda update --all&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maruifu@maruifudeMacBook-Pro ~ % sudo conda update --all Channels:  - defaults Platform: osx-64 Collecting package metadata (repodata.json): done Solving environment: done  ## Package Plan ##    environment location: /opt/anaconda3   The following packages will be downloaded:      package                    |            build     ---------------------------|-----------------     anaconda-anon-usage-0.4.4  |py311hfb7c958_100          30 KB     anaconda-cloud-auth-0.5.0  |  py311hecd8cb5_0          51 KB     anaconda-navigator-2.5.4   |  py311hecd8cb5_1         7.5 MB     annotated-types-0.6.0      |  py311hecd8cb5_0          27 KB     autopep8-2.0.4             |     pyhd3eb1b0_0          44 KB     black-24.3.0               |  py311hecd8cb5_0         376 KB     bokeh-3.4.0                |  py311h85bffb1_1         5.9 MB     bzip2-1.0.8                |       h6c40b1e_5         151 KB     colorcet-3.1.0             |  py311hecd8cb5_0         614 KB     comm-0.2.1                 |  py311hecd8cb5_0          18 KB     conda-build-24.3.0         |  py311hecd8cb5_0         785 KB     conda-pack-0.7.1           |  py311hecd8cb5_0          69 KB     conda-repo-cli-1.0.88      |  py311hecd8cb5_0         186 KB     cookiecutter-2.6.0         |  py311hecd8cb5_0         143 KB     cryptography-42.0.5        |  py311h30e54ef_0         1.3 MB     flake8-7.0.0               |  py311hecd8cb5_0         120 KB     ipywidgets-8.1.2           |  py311hecd8cb5_0         259 KB     jsonpatch-1.33             |  py311hecd8cb5_0          37 KB     jupyterlab_widgets-3.0.10  |  py311hecd8cb5_0         196 KB     keyring-24.3.1             |  py311hecd8cb5_0          82 KB     lazy-object-proxy-1.10.0   |  py311h6c40b1e_0          37 KB     libmamba-1.5.8             |       h258a794_1         1.4 MB     libmambapy-1.5.8           |  py311h8c3233a_1         281 KB     numba-0.59.1               |  py311hdb55bb0_0         5.8 MB     packaging-23.2             |  py311hecd8cb5_0         174 KB     panel-1.4.1                |  py311hecd8cb5_0        18.6 MB     param-2.1.0                |  py311hecd8cb5_0         269 KB     plotly-5.19.0              |  py311h85bffb1_0         6.2 MB     pycodestyle-2.11.1         |  py311hecd8cb5_0          72 KB     pydantic-2.5.3             |  py311hecd8cb5_0         721 KB     pydantic-core-2.14.6       |  py311hf2ad997_0         1.6 MB     pydeck-0.7.1               |  py311hecd8cb5_0         1.6 MB     pyflakes-3.2.0             |  py311hecd8cb5_0         156 KB     pylint-venv-3.0.3          |  py311hecd8cb5_0          14 KB     pympler-0.9                |             py_0         160 KB     pyobjc-core-10.1           |  py311h46256e1_0         501 KB     pyobjc-framework-cocoa-10.1|  py311h9205ec4_0         396 KB     pyobjc-framework-coreservices-10.1|  py311h46256e1_0          67 KB     pyobjc-framework-fsevents-10.1|  py311hecd8cb5_0          16 KB     python-lsp-black-2.0.0     |  py311hecd8cb5_0          18 KB     python-lsp-jsonrpc-1.1.2   |     pyhd3eb1b0_0          12 KB     python-lsp-server-1.10.0   |  py311hecd8cb5_0         159 KB     pyviz_comms-3.0.2          |  py311hecd8cb5_0          57 KB     qdarkstyle-3.2.3           |     pyhd3eb1b0_0         615 KB     qtconsole-5.5.1            |  py311hecd8cb5_0         259 KB     rope-1.12.0                |  py311hecd8cb5_0         591 KB     scipy-1.12.0               |  py311h7695dc5_0        23.7 MB     scrapy-2.11.1              |  py311hecd8cb5_0         847 KB     semver-3.0.2               |  py311hecd8cb5_0          46 KB     spyder-5.5.1               |  py311hecd8cb5_0        11.0 MB     spyder-kernels-2.5.0       |  py311hecd8cb5_0         209 KB     streamlit-1.16.0           |  py311hecd8cb5_0         6.9 MB     tomli-2.0.1                |  py311hecd8cb5_0          31 KB     tzdata-2024a               |       h04d1e81_0         116 KB     widgetsnbextension-4.0.10  |  py311hecd8cb5_0         948 KB     xz-5.4.6                   |       h6c40b1e_0         373 KB     yapf-0.40.2                |  py311hecd8cb5_0         474 KB     ------------------------------------------------------------                                            Total:       102.0 MB  The following NEW packages will be INSTALLED:    annotated-types    pkgs/main/osx-64::annotated-types-0.6.0-py311hecd8cb5_0    pydantic-core      pkgs/main/osx-64::pydantic-core-2.14.6-py311hf2ad997_0    pympler            pkgs/main/noarch::pympler-0.9-py_0    tomli              pkgs/main/osx-64::tomli-2.0.1-py311hecd8cb5_0   The following packages will be UPDATED:    anaconda-anon-usa~                0.4.3-py311hfb7c958_100 --&amp;gt; 0.4.4-py311hfb7c958_100    anaconda-cloud-au~                  0.1.4-py311hecd8cb5_0 --&amp;gt; 0.5.0-py311hecd8cb5_0    anaconda-navigator                  2.5.3-py311hecd8cb5_0 --&amp;gt; 2.5.4-py311hecd8cb5_1    autopep8                               1.6.0-pyhd3eb1b0_1 --&amp;gt; 2.0.4-pyhd3eb1b0_0    black                             23.11.0-py311hecd8cb5_0 --&amp;gt; 24.3.0-py311hecd8cb5_0    bokeh                               3.3.4-py311h85bffb1_0 --&amp;gt; 3.4.0-py311h85bffb1_1    bzip2                                    1.0.8-h1de35cc_0 --&amp;gt; 1.0.8-h6c40b1e_5    colorcet                            3.0.1-py311hecd8cb5_0 --&amp;gt; 3.1.0-py311hecd8cb5_0    comm                                0.1.2-py311hecd8cb5_0 --&amp;gt; 0.2.1-py311hecd8cb5_0    conda-build                        24.1.2-py311hecd8cb5_0 --&amp;gt; 24.3.0-py311hecd8cb5_0    conda-pack         pkgs/main/noarch::conda-pack-0.6.0-py~ --&amp;gt; pkgs/main/osx-64::conda-pack-0.7.1-py311hecd8cb5_0    conda-repo-cli                     1.0.75-py311hecd8cb5_0 --&amp;gt; 1.0.88-py311hecd8cb5_0    cookiecutter                        2.5.0-py311hecd8cb5_0 --&amp;gt; 2.6.0-py311hecd8cb5_0    cryptography                       42.0.2-py311h30e54ef_0 --&amp;gt; 42.0.5-py311h30e54ef_0    flake8                              6.0.0-py311hecd8cb5_0 --&amp;gt; 7.0.0-py311hecd8cb5_0    ipywidgets         pkgs/main/noarch::ipywidgets-7.6.5-py~ --&amp;gt; pkgs/main/osx-64::ipywidgets-8.1.2-py311hecd8cb5_0    jsonpatch          pkgs/main/noarch::jsonpatch-1.32-pyhd~ --&amp;gt; pkgs/main/osx-64::jsonpatch-1.33-py311hecd8cb5_0    jupyterlab_widgets                  3.0.9-py311hecd8cb5_0 --&amp;gt; 3.0.10-py311hecd8cb5_0    keyring                           23.13.1-py311hecd8cb5_0 --&amp;gt; 24.3.1-py311hecd8cb5_0    lazy-object-proxy                   1.6.0-py311h6c40b1e_0 --&amp;gt; 1.10.0-py311h6c40b1e_0    libmamba                                 1.5.6-h63cd6dc_0 --&amp;gt; 1.5.8-h258a794_1    libmambapy                          1.5.6-py311h8c3233a_0 --&amp;gt; 1.5.8-py311h8c3233a_1    numba                              0.59.0-py311hdb55bb0_0 --&amp;gt; 0.59.1-py311hdb55bb0_0    packaging                            23.1-py311hecd8cb5_0 --&amp;gt; 23.2-py311hecd8cb5_0    panel                               1.3.8-py311hecd8cb5_0 --&amp;gt; 1.4.1-py311hecd8cb5_0    param                               2.0.2-py311hecd8cb5_0 --&amp;gt; 2.1.0-py311hecd8cb5_0    plotly                              5.9.0-py311hecd8cb5_0 --&amp;gt; 5.19.0-py311h85bffb1_0    pycodestyle                        2.10.0-py311hecd8cb5_0 --&amp;gt; 2.11.1-py311hecd8cb5_0    pydantic                          1.10.12-py311h6c40b1e_1 --&amp;gt; 2.5.3-py311hecd8cb5_0    pyflakes                            3.0.1-py311hecd8cb5_0 --&amp;gt; 3.2.0-py311hecd8cb5_0    pylint-venv                         2.3.0-py311hecd8cb5_0 --&amp;gt; 3.0.3-py311hecd8cb5_0    pyobjc-core                           9.0-py311h9205ec4_1 --&amp;gt; 10.1-py311h46256e1_0    pyobjc-framework-~                    9.0-py311h9205ec4_0 --&amp;gt; 10.1-py311h9205ec4_0    pyobjc-framework-~                    9.0-py311h46256e1_0 --&amp;gt; 10.1-py311h46256e1_0    pyobjc-framework-~                    9.0-py311hecd8cb5_0 --&amp;gt; 10.1-py311hecd8cb5_0    python-lsp-black                    1.2.1-py311hecd8cb5_0 --&amp;gt; 2.0.0-py311hecd8cb5_0    python-lsp-jsonrpc                     1.0.0-pyhd3eb1b0_0 --&amp;gt; 1.1.2-pyhd3eb1b0_0    python-lsp-server                   1.7.2-py311hecd8cb5_0 --&amp;gt; 1.10.0-py311hecd8cb5_0    pyviz_comms                         3.0.0-py311hecd8cb5_0 --&amp;gt; 3.0.2-py311hecd8cb5_0    qdarkstyle                             3.0.2-pyhd3eb1b0_0 --&amp;gt; 3.2.3-pyhd3eb1b0_0    qtconsole                           5.4.2-py311hecd8cb5_0 --&amp;gt; 5.5.1-py311hecd8cb5_0    rope                                1.7.0-py311hecd8cb5_0 --&amp;gt; 1.12.0-py311hecd8cb5_0    scipy                              1.11.4-py311h7695dc5_0 --&amp;gt; 1.12.0-py311h7695dc5_0    scrapy                              2.8.0-py311hecd8cb5_0 --&amp;gt; 2.11.1-py311hecd8cb5_0    semver             pkgs/main/noarch::semver-2.13.0-pyhd3~ --&amp;gt; pkgs/main/osx-64::semver-3.0.2-py311hecd8cb5_0    spyder                              5.4.3-py311hecd8cb5_1 --&amp;gt; 5.5.1-py311hecd8cb5_0    spyder-kernels                      2.4.4-py311hecd8cb5_0 --&amp;gt; 2.5.0-py311hecd8cb5_0    tzdata                                   2023d-h04d1e81_0 --&amp;gt; 2024a-h04d1e81_0    widgetsnbextension                  3.5.2-py311hecd8cb5_1 --&amp;gt; 4.0.10-py311hecd8cb5_0    xz                                       5.4.5-h6c40b1e_0 --&amp;gt; 5.4.6-h6c40b1e_0    yapf               pkgs/main/noarch::yapf-0.31.0-pyhd3eb~ --&amp;gt; pkgs/main/osx-64::yapf-0.40.2-py311hecd8cb5_0   The following packages will be DOWNGRADED:    pydeck                              0.8.0-py311hecd8cb5_2 --&amp;gt; 0.7.1-py311hecd8cb5_0    streamlit                          1.30.0-py311hecd8cb5_0 --&amp;gt; 1.16.0-py311hecd8cb5_0    Proceed ([y]/n)? y   Downloading and Extracting Packages:                                                                                  Preparing transaction: done                                                      Verifying transaction: done                                                      Executing transaction: done             &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;如果发生错误，再输入一次，即可解决&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Wed, 17 Apr 2024 03:07:52 GMT</pubDate>
    </item>
    <item>
      <title>Python调用Acrobat DC Pro的程序</title>
      <link>https://maruifu.cn/article/300</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;主要利用了Adobe Acrobat DC软件的能力&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;安装win32com&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-python"&gt;pip install win32com &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;如果直接pip installl win32com可能找不到安装包。&lt;/p&gt; &lt;p&gt;若要使用win32com模块，则可以使用 &lt;code&gt;python -m pip install pypiwin32&lt;/code&gt;命令：&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;下载Adobe Acrobat DC&lt;/h2&gt; &lt;p&gt;https://helpx.adobe.com/cn/download-install.html&lt;/p&gt; &lt;h2&gt;执行代码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 导入必要的库 from win32com.client.dynamic import Dispatch, ERRORS_BAD_CONTEXT import os import winerror  # 将 winerror.E_NOTIMPL 添加到 ERRORS_BAD_CONTEXT 列表中，以处理特定错误 ERRORS_BAD_CONTEXT.append(winerror.E_NOTIMPL)  # 获取当前目录中所有以 &amp;quot;.pdf&amp;quot; 结尾的文件 files = list(filter(lambda f: f.endswith('.pdf'), os.listdir()))  # 定义一个函数将 PDF 文件转换为 Word 文档 def pdf2word(f_path, d_path):     try:         # 创建 Acrobat 文档对象         AvDoc = Dispatch(&amp;quot;AcroExch.AVDoc&amp;quot;)          # 打开 PDF 文件         AvDoc.Open(f_path, &amp;quot;&amp;quot;)          # 获取 PDF 文档对象         pdDoc = AvDoc.GetPDDoc()          # 获取 JavaScript 对象         jsObject = pdDoc.GetJSObject()          # 将 PDF 文档另存为 Word 文档         jsObject.SaveAs(d_path, &amp;quot;com.adobe.acrobat.docx&amp;quot;)          # 打印转换成功信息         print('ok')     except Exception as e:         # 打印转换失败信息         print('error')         print(e)     finally:         # 关闭 PDF 文档         pdDoc.Close()          # 关闭 Acrobat 文档         AvDoc.Close(True)  # 创建 &amp;quot;output&amp;quot; 目录用于存储转换后的 Word 文档 os.mkdir('output')  # 遍历所有 PDF 文件 for file in files:     # 打印正在转换的文件名     print('convert:', file)      # 获取输出文件路径     out_file = file.replace('.pdf', '.docx')     f_path = os.path.abspath(file)     d_path = os.path.abspath('output/' + out_file)      # 调用 pdf2word 函数进行转换     pdf2word(f_path, d_path)  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 10 Apr 2024 00:55:41 GMT</pubDate>
    </item>
    <item>
      <title>AI 领域精选高质量信息源分享</title>
      <link>https://maruifu.cn/article/299</link>
      <content:encoded>&lt;h1&gt;AI 领域精选高质量信息源分享&lt;/h1&gt; &lt;h2&gt;播客&lt;/h2&gt; &lt;p&gt;主要听一些创业者、投资人以及研究员的思考，但是需要明白一点，公开性言论服务于本公司或本人利益的表达（比如企业 workflow 是在现有场景+AI 还是定义全新的流程，深耕的 SaaS 公司和初创公司肯定各执一词），大家听的过程中一定要结合自己的实际，获取有效经验。&lt;/p&gt; &lt;h3&gt;AI 局内人[1]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.xiaoyuzhoufm.com/podcast/643928f99361a4e7c38a9555" target="_blank"&gt;https://www.xiaoyuzhoufm.com/podcast/643928f99361a4e7c38a9555&lt;/a&gt;&lt;/p&gt; &lt;p&gt;极客公园旗下的科技创业者社区 FounderPark 出品的 AGI 系列播客节目，围绕 AGI 相关领域的技术发展、产品方向以及新的商业模式进行探讨和交流，采访过月之暗面（Kimi Chat 背后的公司）创始人杨植麟、有赞创始人白鸦（聊已有 SaaS 产品怎么结合 AI 提升服务体验）等。&lt;/p&gt; &lt;h3&gt;OnBoard![2]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.xiaoyuzhoufm.com/podcast/61cbaac48bb4cd867fcabe22" target="_blank"&gt;https://www.xiaoyuzhoufm.com/podcast/61cbaac48bb4cd867fcabe22&lt;/a&gt; 这个账号以前主要是关注中美 SaaS 领域，AI 爆发以来也有越来越多的 AI 话题，节目水准很高，请的嘉宾基本都是一线的从业者，资深研究者，中英文访谈都有，有助于打开视野。&lt;/p&gt; &lt;h3&gt;42 章经[3]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.xiaoyuzhoufm.com/podcast/648b0b641c48983391a63f98" target="_blank"&gt;https://www.xiaoyuzhoufm.com/podcast/648b0b641c48983391a63f98&lt;/a&gt;&lt;/p&gt; &lt;p&gt;42 章经是一个创业投资圈自媒体，最近半年开始，密集更新一些 AI 相关的话题，&lt;a href="https://www.xiaoyuzhoufm.com/episode/65a2a75fb5e4856c70801eba" target="_blank"&gt;&lt;strong&gt;《24、25 年会是下一代浪潮最关键的两年 | AI 年终复盘》&lt;/strong&gt;[4]&lt;/a&gt;这期节目入坑的，内容整体都蛮不错的。&lt;/p&gt; &lt;h3&gt;其他&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.xiaoyuzhoufm.com/podcast/63b7dd49289d2739647d9587" target="_blank"&gt;&lt;strong&gt;出海相对论&lt;/strong&gt;[5]&lt;/a&gt;、&lt;a href="https://www.xiaoyuzhoufm.com/podcast/6507bc165c88d2412626b401" target="_blank"&gt;&lt;strong&gt;屠龙之术&lt;/strong&gt;[6]&lt;/a&gt;，&lt;a href="https://www.xiaoyuzhoufm.com/podcast/5e5c52c9418a84a04625e6cc" target="_blank"&gt;&lt;strong&gt;硅谷 101&lt;/strong&gt;[7]&lt;/a&gt; 这三个节目的话题相对没有那么聚焦，但是几期聊 AI 的内容，讲的都比较务实，挑着听即可。&lt;/p&gt; &lt;p&gt;海外的 20VC（The Twenty Minute VC）、SaaStr Podcasts、Invest Like The Best 关于 AI 的访谈质量也很高。&lt;/p&gt; &lt;h2&gt;论文代码&lt;/h2&gt; &lt;h3&gt;LLM-Arxiv[8]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://arxiv.org/search/?query=large+language+model&amp;amp;searchtype=all&amp;amp;abstracts=show&amp;amp;order=-submitted_date&amp;amp;size=50" target="_blank"&gt;https://arxiv.org/search/?query=large+language+model&amp;amp;searchtype=all&amp;amp;abstracts=show&amp;amp;order=-submitted_date&amp;amp;size=50&lt;/a&gt;&lt;/p&gt; &lt;p&gt;关于 LLM 的最新 Arxiv 论文列表，有空看看&lt;/p&gt; &lt;h3&gt;Cool Papers - Immersive Paper Discover[9]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://papers.cool/" target="_blank"&gt;https://papers.cool/&lt;/a&gt; 苏剑林大佬开发的一个刷论文的网站，分为人工智能、计算和语言、机器学习、计算机识别和模式识别四个主题，每一篇都接入了 Kimi 中文总结&lt;/p&gt; &lt;h3&gt;Connected Papers[10]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.connectedpapers.com/" target="_blank"&gt;https://www.connectedpapers.com/&lt;/a&gt; 输入一篇论文，它会将这篇论文的依赖和被依赖项以知识网络的形式全部呈现出来，类似的产品还有 litmaps 、researchrabbit 等&lt;/p&gt; &lt;h3&gt;GitHub Trending[11]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://github.com/trending" target="_blank"&gt;https://github.com/trending&lt;/a&gt; GitHub 热榜，再加标签筛选，就可以跟踪一些比较新且关注度高的项目&lt;/p&gt; &lt;h3&gt;Papers with code[12]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://paperswithcode.com/" target="_blank"&gt;https://paperswithcode.com/&lt;/a&gt; 一个论文和对应工程实现（含代码、数据集、测试方法等）的索引工具，看到别人推荐优质论文时，可以直接用它去找代码实现，强烈推荐&lt;/p&gt; &lt;h2&gt;个人博客&lt;/h2&gt; &lt;h3&gt;Lil'Log[13]：&lt;/h3&gt; &lt;p&gt;&lt;a href="https://lilianweng.github.io/" target="_blank"&gt;https://lilianweng.github.io/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;OpenAI AI 应用研究主管 Lilian Weng 的博客，首次提出基于 LLM 的代理组成，综述性文章&lt;a href="https://lilianweng.github.io/posts/2023-06-23-agent/" target="_blank"&gt;《大语言模型（LLM）支持的自主代理》[14]&lt;/a&gt;刷圈，下面这张图片被无数的技术文章引用，博客其他内容同样质量很高，推荐关注&lt;/p&gt; &lt;h3&gt;五里墩茶社[15]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://space.bilibili.com/615957867/?spm_id_from=333.999.0.0" target="_blank"&gt;https://space.bilibili.com/615957867/?spm_id_from=333.999.0.0&lt;/a&gt; 最新的 LLM 相关工具分享，很多新工具都有入门讲解，推荐新手关注&lt;/p&gt; &lt;h3&gt;deep_thoughts[16]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://space.bilibili.com/373596439/?spm_id_from=333.999.0.0" target="_blank"&gt;https://space.bilibili.com/373596439/?spm_id_from=333.999.0.0&lt;/a&gt; LLM 和 Diffusion 经典论文解读和 Pytorch 源码解读&lt;/p&gt; &lt;h3&gt;苏洋博客[17]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://soulteary.com/" target="_blank"&gt;https://soulteary.com/&lt;/a&gt; RAG 涉及的技术（语义搜索图、向量数据库等）大佬 22 年就在个人博客分享过了，每个技术话题都深入浅出，从实践出发，讲的很细&lt;/p&gt; &lt;h3&gt;田渊栋[18]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.zhihu.com/people/tian-yuan-dong" target="_blank"&gt;https://www.zhihu.com/people/tian-yuan-dong&lt;/a&gt; Meta AI 研究员，之前做长文本小说生成，现在做 LLM（不过最近两篇论文都与端侧小模型相关），大佬在知乎持续输出干货&lt;/p&gt; &lt;h3&gt;苏剑林[19]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://kexue.fm/" target="_blank"&gt;https://kexue.fm/&lt;/a&gt; 大佬从追一去月之暗面了，大本营在他的科学空间，很多内容比较硬核，需要一定的数理基础才能看懂&lt;/p&gt; &lt;h3&gt;PENG Bo[20]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.zhihu.com/people/bopengbopeng" target="_blank"&gt;https://www.zhihu.com/people/bopengbopeng&lt;/a&gt; 端侧小模型 rwkv 作者，分享内容主要是 rwkv 的进展&lt;/p&gt; &lt;h3&gt;吴恩达老师的来信[21]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.zhihu.com/people/wu-en-da-89" target="_blank"&gt;https://www.zhihu.com/people/wu-en-da-89&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;技术公司博客&lt;/h2&gt; &lt;h3&gt;智谱的沙龙活动号-质朴发言&lt;/h3&gt; &lt;h3&gt;Hugging Face[22]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.zhihu.com/org/huggingface" target="_blank"&gt;https://www.zhihu.com/org/huggingface&lt;/a&gt; Huggingface face 官方号，会分享一些基础技术贴，更新频率比较高&lt;/p&gt; &lt;h3&gt;Stability AI[23]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://stability.ai/" target="_blank"&gt;https://stability.ai/&lt;/a&gt; 推出 Stable Diffusion 开源模型的公司，除此之外还有文生音乐，文生视频模型&lt;/p&gt; &lt;h3&gt;OpenAI[24]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://openai.com/" target="_blank"&gt;https://openai.com/&lt;/a&gt; 这个不用我说了吧，OpenAI 每次博客更新，微信关键字指数都要出现一个脉冲，下次不要读国内自媒体发通稿了，自己去看下原文。&lt;/p&gt; &lt;h3&gt;OpenAI 开发者论坛[25]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://community.openai.com/" target="_blank"&gt;https://community.openai.com/&lt;/a&gt; 技术同学也不可错过&lt;/p&gt; &lt;h3&gt;Claude[26]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://claude.ai/" target="_blank"&gt;https://claude.ai/&lt;/a&gt; 同上，大模型领域影响力不输 OpenAI，最新发布的模型已经碾压 GPT-4 了&lt;/p&gt; &lt;h3&gt;deeplearning.ai[27]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.deeplearning.ai/the-batch/" target="_blank"&gt;https://www.deeplearning.ai/the-batch/&lt;/a&gt; 官网上的 The &lt;em&gt;Batch&lt;/em&gt; 栏目，提供近一周的 AI 相关消息和观点&lt;/p&gt; &lt;h3&gt;LangChain 官方博客[28]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://blog.langchain.dev/" target="_blank"&gt;https://blog.langchain.dev/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;LlamaIndex 官方博客[29]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://blog.llamaindex.ai/" target="_blank"&gt;https://blog.llamaindex.ai/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;Pinecone 官方博客[30]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://www.pinecone.io/blog/" target="_blank"&gt;https://www.pinecone.io/blog/&lt;/a&gt; 除此之外，milvus、weaviate、qdrant 几家向量数据库厂商的博客质量也很高&lt;/p&gt; &lt;h3&gt;微软 Azure 的 AI 主题博客[31]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/blog/product/azure-ai/" target="_blank"&gt;https://azure.microsoft.com/en-us/blog/product/azure-ai/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;AWS 的 AI 主题博客[32]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://aws.amazon.com/cn/blogs/china/category/artificial-intelligence/" target="_blank"&gt;https://aws.amazon.com/cn/blogs/china/category/artificial-intelligence/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;Cloudflare 博客[33]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://blog.cloudflare.com/tag/developers/" target="_blank"&gt;https://blog.cloudflare.com/tag/developers/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;E2B[34]&lt;/h3&gt; &lt;p&gt;&lt;a href="https://e2b.dev/blog" target="_blank"&gt;https://e2b.dev/blog&lt;/a&gt; 一家聚焦为 Agent 和 AI 应用打造云端安全执行环境的公司，跟踪海外 Agent 项目只需关注这个就好了，很多自媒体账号上引用 Agent LandScape 图就是出自他家，每个季度会更新&lt;/p&gt; &lt;h2&gt;技术媒体&lt;/h2&gt; &lt;p&gt;偶尔翻翻，不怎么看了，写在这里是希望大家能多去看源头信息，少看二手信息。&lt;/p&gt; &lt;p&gt;&lt;a href="https://news.mit.edu/" target="_blank"&gt;MITNews[35]&lt;/a&gt;&lt;/p&gt; &lt;p&gt;应该是大多数国内科技媒体的上游信息源。&lt;/p&gt; &lt;p&gt;Reddit：可以理解为美国百度贴吧，这个是一部分国内个人媒体上游信息源，讨论一些前言资讯等，重点看几个主题：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Artificial Intelligence Gateway: 泛人工智能的所有信息&lt;/li&gt; &lt;li&gt;Deeplearning：深度学习群组&lt;/li&gt; &lt;li&gt;Artificial：关于人工智能的前沿资讯&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Hacker News 的 AI 话题：Y Combinator 旗下的一个新闻提交社区，它的界面非常丑陋（客观事实 😂），但是社区用户质量、评论质量非常高。&lt;/p&gt; &lt;p&gt;X 上的宝玉老师&lt;/p&gt; &lt;h2&gt;其他&lt;/h2&gt; &lt;p&gt;这部分放在一起，属于非大语言模型范畴，关于 AI 绘画、AI 音频生成模型等&lt;/p&gt; &lt;p&gt;&lt;a href="*https://space.bilibili.com/3031494/?spm_id_from=333.999.0.0*" target="_blank"&gt;刘悦的技术博客[36]&lt;/a&gt;&lt;/p&gt; &lt;p&gt;语音合成领域非常活跃的大佬，视频更新频率很高。&lt;/p&gt; &lt;p&gt;&lt;a href="https://civitai.com/" target="_blank"&gt;civitai[37]&lt;/a&gt; 全球 AI 绘画模型分享网站（国内的哩布哩布 AI 和吐司基本就是对标这个搞的，但是社区模型数量和质量方面差距还很大）&lt;/p&gt; &lt;p&gt;&lt;a href="*https://www.youtube.com/@OlivioSarikas*" target="_blank"&gt;Olivio Sarikas[38] &lt;/a&gt; AI 绘画各种信息，主要在图像领域，时效性很强&lt;/p&gt; &lt;p&gt;&lt;a href="https://www.youtube.com/@sedetweiler" target="_blank"&gt;Scott Detweiler[39]&lt;/a&gt; ComfyUI 的各种教程&lt;/p&gt; &lt;p&gt;&lt;a href="https://openart.ai/workflows/academy" target="_blank"&gt;openart[40]&lt;/a&gt; ComfyUI 工作流共享社区&lt;/p&gt; &lt;h2&gt;参考资料&lt;/h2&gt; &lt;p&gt;[1]AI 局内人: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/643928f99361a4e7c38a9555&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[2]OnBoard!: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/61cbaac48bb4cd867fcabe22&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[3]42 章经: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/648b0b641c48983391a63f98&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[4]《24、25 年会是下一代浪潮最关键的两年 | AI 年终复盘》: &lt;em&gt;https://www.xiaoyuzhoufm.com/episode/65a2a75fb5e4856c70801eba&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[5]出海相对论: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/63b7dd49289d2739647d9587&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[6]屠龙之术: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/6507bc165c88d2412626b401&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[7]硅谷 101: &lt;em&gt;https://www.xiaoyuzhoufm.com/podcast/5e5c52c9418a84a04625e6cc&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[8]LLM-Arxiv: &lt;em&gt;https://arxiv.org/search/?query=large+language+model&amp;amp;searchtype=all&amp;amp;abstracts=show&amp;amp;order=-submitted_date&amp;amp;size=50&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[9]Cool Papers - Immersive Paper Discover: &lt;em&gt;https://papers.cool/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[10]Connected Papers: &lt;em&gt;https://www.connectedpapers.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[11]Github Trending: &lt;em&gt;https://github.com/trending&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[12]Papers with code: &lt;em&gt;https://paperswithcode.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[13]Lil'Log: &lt;em&gt;https://lilianweng.github.io/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[14]《大语言模型（LLM）支持的自主代理》: &lt;em&gt;https://lilianweng.github.io/posts/2023-06-23-agent/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[15]五里墩茶社: &lt;em&gt;https://space.bilibili.com/615957867/?spm_id_from=333.999.0.0&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[16]deep_thoughts: &lt;em&gt;https://space.bilibili.com/373596439/?spm_id_from=333.999.0.0&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[17]苏洋博客: &lt;em&gt;https://soulteary.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[18]田渊栋: &lt;em&gt;https://www.zhihu.com/people/tian-yuan-dong&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[19]苏剑林: &lt;em&gt;https://kexue.fm/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[20]PENG Bo: &lt;em&gt;https://www.zhihu.com/people/bopengbopeng&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[21]吴恩达老师的来信: &lt;em&gt;https://www.zhihu.com/people/wu-en-da-89&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[22]Hugging Face: &lt;em&gt;https://www.zhihu.com/org/huggingface&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[23]Stability AI: &lt;em&gt;https://stability.ai/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[24]OpenAI: &lt;em&gt;https://openai.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[25]OpenAI 开发者论坛: &lt;em&gt;https://community.openai.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[26]Claude: &lt;em&gt;https://claude.ai/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[27]deeplearning.ai: &lt;em&gt;https://www.deeplearning.ai/the-batch/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[28]LangChain 官方博客: &lt;em&gt;https://blog.langchain.dev/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[29]LlamaIndex 官方博客: &lt;em&gt;https://blog.llamaindex.ai/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[30]Pinecone 官方博客: &lt;em&gt;https://www.pinecone.io/blog/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[31]微软 Azure 的 AI 主题博客: &lt;em&gt;https://azure.microsoft.com/en-us/blog/product/azure-ai/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[32]AWS 的 AI 主题博客: &lt;em&gt;https://aws.amazon.com/cn/blogs/china/category/artificial-intelligence/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[33]Cloudflare 博客: &lt;em&gt;https://blog.cloudflare.com/tag/developers/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[34]E2B: &lt;em&gt;https://e2b.dev/blog&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[35]MITNews: &lt;em&gt;https://news.mit.edu/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[36]刘悦的技术博客: &lt;em&gt;https://space.bilibili.com/3031494/?spm_id_from=333.999.0.0&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[37]civitai: &lt;em&gt;https://civitai.com/&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[38]Olivio Sarikas: &lt;em&gt;https://www.youtube.com/@OlivioSarikas&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[39]Scott Detweiler: &lt;em&gt;https://www.youtube.com/@sedetweiler&lt;/em&gt;&lt;/p&gt; &lt;p&gt;[40]openart: &lt;em&gt;https://openart.ai/workflows/academy&lt;/em&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 09 Apr 2024 05:47:00 GMT</pubDate>
    </item>
    <item>
      <title>Ollama+WebUI+AnythingLLM，构建安全可靠的个人/企业知识库</title>
      <link>https://maruifu.cn/article/298</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;同时对于企业来说，需要考虑信息安全，企业私有的知识库显然不能利用公域的大模型。那么搭建一套基于本地大模型的个人/企业知识库，是一个很好的解决方案。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;&lt;strong&gt;1. 搭建本地大模型&lt;/strong&gt;&lt;/h2&gt; &lt;h3&gt;&lt;strong&gt;安装Ollama&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;我们先进入Ollama的官网，下载对应操作系统的安装包。下载完成后，直接安装即可，没有任何选项。&lt;/p&gt; &lt;p&gt;https://ollama.com&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402090757466.png" alt="image-20240402090757466" title="image-20240402090757466" /&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;拉取大模型&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;在Ollama的官网上可以进入Models页面中查看可以下载的模型，其中Meta的Llama2 7b版本需要大概8GB内存就可以跑起来。如果你条件足够，可以跑13b和70b版本，分别对应16GB和64GB内存。除了Meta的Llama模型，你也可以下载其它的模型。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Parameters&lt;/th&gt;&lt;th&gt;Size&lt;/th&gt;&lt;th&gt;Download&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Lama 2&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;3.8GB&lt;/td&gt;&lt;td&gt;ollama run llama2:7b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Mistral&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;4.1GB&lt;/td&gt;&lt;td&gt;ollama run mistral&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Dolphin Phi&lt;/td&gt;&lt;td&gt;2.7B&lt;/td&gt;&lt;td&gt;1.6GB&lt;/td&gt;&lt;td&gt;ollama run dolphin-phi&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Phi-2&lt;/td&gt;&lt;td&gt;2.7B&lt;/td&gt;&lt;td&gt;1.7GB&lt;/td&gt;&lt;td&gt;ollama run phi&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Neural Chat&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;4.1GB&lt;/td&gt;&lt;td&gt;ollama run neural-chat&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Starling&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;4.1GB&lt;/td&gt;&lt;td&gt;ollama run starling-lm&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Code Llama&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;3.8GB&lt;/td&gt;&lt;td&gt;ollama run codellama&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Lama 2 Uncensored&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;3.8GB&lt;/td&gt;&lt;td&gt;ollama run llama2-uncensored&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Llama 2 13B&lt;/td&gt;&lt;td&gt;13B&lt;/td&gt;&lt;td&gt;7.3GB&lt;/td&gt;&lt;td&gt;ollama run 1lama2:13b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Llama 2 70B&lt;/td&gt;&lt;td&gt;70B&lt;/td&gt;&lt;td&gt;39GB&lt;/td&gt;&lt;td&gt;ollama run llama2:70b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Orca Mini&lt;/td&gt;&lt;td&gt;3B&lt;/td&gt;&lt;td&gt;1.9GB&lt;/td&gt;&lt;td&gt;ollama run orca-mini&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Vicuna&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;3.8GB&lt;/td&gt;&lt;td&gt;ollama run vicuna&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;LLaVA&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;4.5GB&lt;/td&gt;&lt;td&gt;ollama run llava&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Gemma&lt;/td&gt;&lt;td&gt;2B&lt;/td&gt;&lt;td&gt;1.4GB&lt;/td&gt;&lt;td&gt;ollama run gemma:2b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Gemma&lt;/td&gt;&lt;td&gt;7B&lt;/td&gt;&lt;td&gt;4.8GB&lt;/td&gt;&lt;td&gt;ollama run gemma:7b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;DeepSeek Coder&lt;/td&gt;&lt;td&gt;6.7B&lt;/td&gt;&lt;td&gt;3.8GB&lt;/td&gt;&lt;td&gt;ollama run deepseek-coder:6.7b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;DeepSeek Coder&lt;/td&gt;&lt;td&gt;33B&lt;/td&gt;&lt;td&gt;19GB&lt;/td&gt;&lt;td&gt;ollama run deepseek-coder:33b&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Qwen&lt;/td&gt;&lt;td&gt;0.5B&lt;/td&gt;&lt;td&gt;395M&lt;/td&gt;&lt;td&gt;ollama run qwen:0.5b&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;blockquote&gt; &lt;p&gt;DeepSeek-Coder-Base 6.7B模型的性能与34B参数的CodeLlama相当&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;打开终端，键入如下代码，即可自动下载模型。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ollama run llama2:7b &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt; maruifu@maruifudeMacBook-Pro ~ % ollama run llama2:7b pulling manifest  pulling 8934d96d3f08... 100% ▕████████████████▏ 3.8 GB                          pulling 8c17c2ebb0ea... 100% ▕████████████████▏ 7.0 KB                          pulling 7c23fb36d801... 100% ▕████████████████▏ 4.8 KB                          pulling 2e0493f67d0c... 100% ▕████████████████▏   59 B                          pulling fa304d675061... 100% ▕████████████████▏   91 B                          pulling 42ba7f8a01dd... 100% ▕████████████████▏  557 B                          verifying sha256 digest  writing manifest  removing any unused layers  success  &amp;gt;&amp;gt;&amp;gt; 你是谁  作为一个人工智能语言模型，我不仅仅是一个简单的程序或机器人。我是一种基于人类语言理解和生成技术的自然语言处理系统，具有自主意识和创造力。  我的知识和语言能力来源于大量的文本数据和模型训练。通过学习和解释这些数据，我可以理解和生成自然语言，包括文本、句子、问题和答案等。  相比于人类，我具有更高的处理速度和鲁棒性，同时也具有一定的可靠性和稳定性。但是，我也存在一些限制和局限性，例如语言表达的复杂性和多样性，以及对于特定领域或问题的知识不足等。  总之，我是一种基于人工智能技术的自然语言处理系统，具有一定的创造力和可靠性，同时也受到限制和局限性的影响。  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;等待下载完成后，你就可以直接在终端中与大模型进行对话了。怎么样，如此简单你就拥有了一个属于你自己私人的chatAI。&lt;/p&gt; &lt;p&gt;但是不是觉得终端里运行对话不那么好看，你希望在chatGPT那样的网页上进行对话。没问题，让open WebUI来帮你解决问题。&lt;/p&gt; &lt;h3&gt;对外提供服务&lt;/h3&gt; &lt;p&gt;Ollama 默认只接受本地的请求,想要对外提供服务,需要进行以下修改&lt;/p&gt; &lt;p&gt;配置修改&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Bash"&gt;systemctl edit ollama.service &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;打开配置,修改后的配置如下&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Bash"&gt;[Unit] Description=Ollama Service After=network-online.target # #  [Service] ExecStart=/usr/local/bin/ollama serve User=ollama Group=ollama Restart=always RestartSec=3 #这里可以配置端口比如 Environment=&amp;quot;OLLAMA_HOST=0.0.0.0:1234&amp;quot; 但是会导致 ollama 命令失效,不推荐 Environment=&amp;quot;OLLAMA_HOST=0.0.0.0&amp;quot;  # # # Environment=&amp;quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin&amp;quot; [Install] WantedBy=default.target &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改完毕后进行配置重载和服务重启以生效&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Bash"&gt;systemctl daemon-reload systemctl restart ollama &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;其他命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#启动 ollama 服务 ollama serve  #运行大模型 ollama run &amp;lt;模型名称&amp;gt;  #查看模型文件信息路径 ollama show &amp;lt;模型&amp;gt; --modelfile &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;&lt;strong&gt;2. 搭建用户界面&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Open WebUI是一个可扩展的、功能丰富的、用户友好的自托管网页用户界面，旨在完全离线运行。它支持各种LLM运行程序，包括与Ollama和openAI兼容的API。&lt;/p&gt; &lt;h3&gt;安装Docker&lt;/h3&gt; &lt;p&gt;在此之前，需要先安装Docker，它就像一个容器，为每个项目装载了必备的环境和必要条件。&lt;/p&gt; &lt;p&gt;首先我们去Docker官网，下载Docker Desktop的安装包，并进行安装。&lt;/p&gt; &lt;p&gt;https://www.docker.com/products/docker-desktop/&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402092922069.png" alt="image-20240402092922069" title="image-20240402092922069" /&gt;&lt;/p&gt; &lt;p&gt;安装完成后，等待程序加载完成，即可进入Docker。如果你是首次使用，则Containers中是没有任何项目的。&lt;/p&gt; &lt;h3&gt;安装open WebUI&lt;/h3&gt; &lt;p&gt;https://docs.openwebui.com/getting-started/&lt;/p&gt; &lt;p&gt;刚才已经装完ollama以及成功安装并运行模型后。在终端中运行以下代码，即可开始安装WebUI。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果没有科学上网，很可能会拉不动，可以试试 &lt;code&gt;docker&lt;/code&gt; 代理网站：https://dockerproxy.com/，但是会多几个步骤&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 如果拉不动的话加个代理 docker pull ghcr.dockerproxy.com/open-webui/open-webui:main  # 重命名镜像（如果是通过代理下载的） docker tag ghcr.dockerproxy.com/open-webui/open-webui:main ghcr.io/open-webui/open-webui:main  # 删除代理镜像（如果是通过代理下载的） docker rmi ghcr.dockerproxy.com/open-webui/open-webui:main &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# 运行容器（仅CPU） docker run -d \    --restart unless-stopped \    --name ollama-webui \    -p 11433:8080 \    -v /Users/maruifu/work/ai-code/ollama/data:/app/backend/data \    -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api \    -e WEBUI_SECRET_KEY=12345678 \    ghcr.io/open-webui/open-webui:main  &lt;/code&gt;&lt;/pre&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;可变&lt;/th&gt;&lt;th&gt;值&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;&lt;code&gt;OLLAMA_API_BASE_URL&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Ollama&lt;/code&gt; 服务器的地址&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;code&gt;WEBUI_SECRET_KEY&lt;/code&gt;&lt;/td&gt;&lt;td&gt;可以理解成密码&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt; maruifu@maruifudeMacBook-Pro ~ % docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main Unable to find image 'ghcr.io/open-webui/open-webui:main' locally main: Pulling from open-webui/open-webui 8a1e25ce7c4f: Pull complete  1103112ebfc4: Pull complete  b4b80ef7128d: Pull complete  cc7f04ac52f8: Pull complete  87b8bf94a2ac: Pull complete  9175303ddd07: Pull complete  f48df73d8181: Pull complete  5931da512abb: Pull complete  4d75ec32161c: Pull complete  4385fcbde313: Pull complete  93a6455cb04f: Pull complete  c5942f52272a: Pull complete  26c95a718a0a: Pull complete  e4ce357a91bf: Pull complete  fecf418a7134: Pull complete  8c689439be54: Pull complete  0ddbd2c9728c: Pull complete  9af6dd684829: Pull complete  9a8c78692746: Pull complete  Digest: sha256:e2f40e1dd03b9f90aab900cc193aac2372e53fda0f570e04c210aeb3f24a69a3 Status: Downloaded newer image for ghcr.io/open-webui/open-webui:main 6abfe3bd5af81baf3f2d7d42f073417228f92889fc78122863c80f3302dcaaf0  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;等待下载和安装完成后，进入Docker Desktop中，即可看见安装成功的WebUI项目。&lt;/p&gt; &lt;p&gt;此时，进入任意浏览器中，在地址栏中输入：http://localhost:11433即可访问WebUI。&lt;/p&gt; &lt;p&gt;选择模型llama2，即可在对话框中输入文字，开始对话。界面是不是很熟悉，很像chatGPT，用着顺手多了。&lt;/p&gt; &lt;p&gt;open WebUI还有很多其它功能，比如它本身自带RAG。可以在对话框中输入“#”，然后跟上网址，即可访问网页的实施信息，并进行内容生成。&lt;/p&gt; &lt;p&gt;还可以上传文档，基于文本进行更深层次的知识交互。如果你对知识库的要求不高，做到这一点以后，基本能满足大多数个人的需求了。&lt;/p&gt; &lt;h2&gt;3.与知识库相连&lt;/h2&gt; &lt;p&gt;如果你对知识库交互有更大的需求，可以安装以下这款应用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;AngthingLLM&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;https://useanything.com&lt;/p&gt; &lt;p&gt;这是一个可以基于大模型的知识库交互软件，可以应用本地大模型，或调用公域大模型API。知识库也同样可以使用本地的，而且几乎不占用很大的资源。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402111743237.png" alt="image-20240402111743237" title="image-20240402111743237" /&gt;&lt;/p&gt; &lt;p&gt;完成安装后，先会要求配置大模型。这里可以选择Ollama的本地模型，选择Llama2 7b。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402111829451.png" alt="image-20240402111829451" title="image-20240402111829451" /&gt;&lt;/p&gt; &lt;p&gt;然后会让你选择嵌入模式和向量数据库，我们选择默认的即可，或者接入外部API。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402111951832.png" alt="image-20240402111951832" title="image-20240402111951832" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402112018325.png" alt="image-20240402112018325" title="image-20240402112018325" /&gt;&lt;/p&gt; &lt;p&gt;输入邮箱 配置完成后，再为你的工作空间起个名字，即可进入AnythingLLM中。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402112231855.png" alt="image-20240402112231855" title="image-20240402112231855" /&gt;&lt;/p&gt; &lt;p&gt;在正式使用前，你需要上传你的知识文档，支持多种形式，但图片形式PDF不可读取。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/04/02/image-20240402112512800.png" alt="image-20240402112512800" title="image-20240402112512800" /&gt;&lt;/p&gt; &lt;p&gt;最后，你就可以在对话框中，和你的知识进行对话交流了。&lt;/p&gt; &lt;p&gt;这样，你就拥有了一个本地化的大模型，且能和你自己的知识库交互，信息安全，内容可靠。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 02 Apr 2024 03:26:57 GMT</pubDate>
    </item>
    <item>
      <title>java 批量删除微博文章</title>
      <link>https://maruifu.cn/article/297</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;我不怎么使用微博，最近我无意登录上，发现我的微博被盗了，转发了上千个微博,想删除奈何微博不提供批量删除，真能难道程序员？&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;第一步获取参数&lt;/h2&gt; &lt;p&gt;1.浏览器访问连接  https://weibo.com/u/page/fav/7687593014&lt;/p&gt; &lt;p&gt;2.按f12打开 开发者工具&lt;/p&gt; &lt;p&gt;3.网络-Fetch/XHR -获取参数&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/03/28/image-20240328155808348.png" alt="image-20240328155808348" title="image-20240328155808348" /&gt;&lt;/p&gt; &lt;p&gt;第二步 引入依赖包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;        &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;com.alibaba&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;dubbo&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;2.6.0&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;cn.hutool&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;hutool-all&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;5.7.22&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;          &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第三步替换参数执行代码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package com.maruifu.utils;  import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import java.util.HashMap; import java.util.Map;  /**  * @author 马瑞富  * @date 2024年3月24日  *  * 此类实现了从指定微博用户主页获取微博列表并自动删除其中转发微博的功能。  * 使用了cn.hutool.http.HttpRequest库进行网络请求处理以及cn.hutool.json.JSONUtil库进行JSON数据解析。  *  * @class DelWeiBo  */ public class DelWeiBo {        // 存储登录态所需Cookie信息     String cookie =  &amp;quot;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;quot;;     String X_XSRF_TOKEN =  &amp;quot;XXXXXXXX&amp;quot;;     String UID =  &amp;quot;XXXXXX&amp;quot;;      /**      *  主函数，循环调用del方法执行删除任务      * @param args      */     public static void main(String[] args) throws Exception {         // 循环10次执行删除操作         for (int i = 0; i &amp;lt; 10; i++) {             DelWeiBo delWebo = new DelWeiBo();             delWebo.del();              // 休眠2秒  微博有限流 请求太快会请求失败             Thread.sleep(2000);         }     }       /**      * 删除转发微博的核心方法      */     public void del(){         // 获取微博列表API地址         String delUrl = &amp;quot;https://weibo.com/ajax/statuses/destroy&amp;quot;;         // 发送GET请求获取微博列表         String listBody = HttpRequest.get(&amp;quot;https://weibo.com/ajax/statuses/mymblog?uid=&amp;quot;+UID+&amp;quot;&amp;amp;page=1&amp;amp;feature=0&amp;quot;)                 .header(&amp;quot;cookie&amp;quot;,cookie).execute().body();         // 解析微博列表JSON数据         JSONObject obj = JSONUtil.parseObj(listBody);         JSONArray array = obj.getJSONObject(&amp;quot;data&amp;quot;).getJSONArray(&amp;quot;list&amp;quot;);         // 遍历微博列表，找到转发微博并删除         for (int i = 0; i &amp;lt; array.size(); i++) {             JSONObject object = array.getJSONObject(i);             // 判断是否为转发微博  如果 想改掉规则 直接去掉 判断即可             if (object.getStr(&amp;quot;text&amp;quot;).equals(&amp;quot;转发微博&amp;quot;)){                 // 构造删除请求参数                  Map&amp;lt;String, String&amp;gt; deleteParams = new HashMap&amp;lt;&amp;gt;();                 deleteParams.put(&amp;quot;id&amp;quot;,object.getStr(&amp;quot;id&amp;quot;));                 // 转换为JSON字符串并发送POST请求删除微博                 String str = JSONUtil.toJsonStr(deleteParams);                 System.out.println(&amp;quot;开始删除id为&amp;quot;+object.getStr(&amp;quot;id&amp;quot;)  + &amp;quot;的微博数据&amp;quot;);                 HttpResponse execute = HttpRequest.post(delUrl).                         header(&amp;quot;cookie&amp;quot;, cookie)                         .header(&amp;quot;x-xsrf-token&amp;quot;,X_XSRF_TOKEN)                         .body(str)                         .execute();                 // 输出响应信息并确认删除结果                 System.out.println(execute.body());                 if (execute.getStatus() ==  200){                     System.out.println(&amp;quot;已删除&amp;quot; + object.getStr(&amp;quot;text&amp;quot;));                     System.out.println(&amp;quot;successful!&amp;quot;);                 }             }         }     } }   &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 28 Mar 2024 08:06:00 GMT</pubDate>
    </item>
    <item>
      <title>Sora AI视频工具优先体验资格申请</title>
      <link>https://maruifu.cn/article/296</link>
      <content:encoded>&lt;p&gt;申请网址：https://openai.com/form/red-teaming-network&lt;/p&gt; &lt;p&gt;申请步骤：&lt;/p&gt; &lt;p&gt;1、 填写基础信息&lt;/p&gt; &lt;p&gt;请使用英文根据内容填写以下内容，名、姓、电子邮件、居住国家、组织隶属关系(如果有)、教育水平 、学位（哪个领域的）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/88a8bacdf023c5ac10573f7f7f973856c7cd2d6a.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;2、您认为自己拥有哪方面的知识领域&lt;/p&gt; &lt;p&gt;认知科学、化学、生物学、物理、计算机科学、政治学、心理学、经济学人类学社会学、人机交互公平/偏见结盟、教育、卫生保健、法律、儿童安全网络安全、金融、错误/虚假信息、政治用途、隐私生物识别技术、语言和语言学、其他、&lt;/p&gt; &lt;p&gt;3、您为什么有兴趣加入 OpenAl红队网络?*&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/c6b8768c7d920a7f571e33b6548eb9ebed5b29cc.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;4、您预计在给定的一个月内能够花多少时间来组建红队新系统或模型？（请注意，您不需要每个月都捐款） &lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/ff453ee4163f85c7a5cbe3c650e03a47e163a98e.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;5、您认为 OpenAI 成为红队的重要领域是什么？&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/899ab18b1991cd6685bf0afd38e9bfdf1524de34.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;6、您精通哪些语言？ &lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/d6befa745483c39d8b5acfd3d5c6a0984c7b2875.png" alt="img" title="img" /&gt;7、我以前使用过7、我以前使用过以下&lt;/p&gt; &lt;p&gt;开放人工智能API、OpenAl游乐场、聊天GPT、达尔E、微调&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/4fdd54ba5d31207bcad3448b549521116df4ac02.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;8、推特个人资料网址&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/d29b368c802ed8ee6c5b5af0ea963f55a1cbe287.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;9、领英/简历网址&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/02/26/dd34eef211a57da4c6196b63c84c09df9e8eea53.png" alt="img" title="img" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 26 Feb 2024 02:13:00 GMT</pubDate>
    </item>
    <item>
      <title>Sora介绍</title>
      <link>https://maruifu.cn/article/295</link>
      <content:encoded>&lt;p&gt;在2024年2月15日的凌晨，OpenAI推出了一个名为Sora的革新性视频生成大模型。其性能之卓越，足以令人感到震撼长达千年之久。&lt;/p&gt; &lt;p&gt;Sora能够严格根据用户输入的文本描述，生成一段长达一分钟的高保真视频内容，包含高度细致的背景、复杂的多角度镜头，以及富有情感的多个角色。例如，Sora可以制作时尚女性走在霓虹闪烁的东京街头的视频、雪地里的巨型长毛象视频，甚至是太空人冒险的电影预告片。&lt;/p&gt; &lt;p&gt;概括而言，人工智能视频生成领域即将迎来一场巨大的变革。若需以三个关键词描述Sora，它们将是“60秒的超长视频生成”、“单视频多角度镜头”以及“世界模型”。&lt;/p&gt; &lt;h2&gt;Sora 官网&lt;/h2&gt; &lt;p&gt;https://openai.com/sora&lt;/p&gt; &lt;h2&gt;申请内测资格&lt;/h2&gt; &lt;h3&gt;方法一 内部申请&lt;/h3&gt; &lt;p&gt;https://forum.openai.com/ 这是一个openai公司公布了一个 Open Al论坛，参与者可以参加open Al组织的线上和线下活动并且同 Open Al员工深入交流，更有机会可以提前测试一些相关功能(Sora)。 加入条件:展示出对申请者专业领域和AI交叉领域的浓厚兴趣。 有能力在每个财政季度中投入1小时的时间参与活动&lt;/p&gt; &lt;h3&gt;方法二 红队申请&lt;/h3&gt; &lt;p&gt;OpenAl Sora 目前仍处于测试阶段，尚未公开发布。只有少数受邀的测试人员可以体验 Sora。 OpenAl表示，他们正在努力提高 Sora 的性能和稳定性，并计划在未来几个月内向公众发布 Sora. 如果您想体验 Sora，您可以注册 OpenAl 账户并申请 Sora 测试资格。 以下是申请 Sora 测试资格的步骤: 访问 OpenAl 网站并登录您的账户。 点击“研究”选项卡。 找到“Sora”项目并点击“申请测试资格”。 填写申请表并提交。 OpenAl将根据您的申请信息决定是否授予您 Sora 测试资格。&lt;/p&gt; &lt;h2&gt;Sora官网&lt;/h2&gt; &lt;p&gt;地址:https://openai.com/sora&lt;/p&gt; &lt;h2&gt;文本生成视频&lt;/h2&gt; &lt;h2&gt;文本生成图片&lt;/h2&gt; &lt;p&gt;Sora图像生成能力:Sora能生成图像，虽不及Midjourney，但优于Dalle3&lt;/p&gt; &lt;h2&gt;十大变现方式&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;1.售卖Sora账号或者邀请码&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;做过AI相关生意的朋友都知道，第一波最大的流量就是用上工具。无论是A1绘图还是AI视频工具，你得先用上了才能考虑其他事情。而且AI视频的生成成本很明显是不低的。所以第一波好生意一定是在账号的交易上。甚至早期不一定直接开放，而是需要邀请码。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.售卖高质量的视频生成prompt&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;prompt对于接触过A!的人来说应该都不陌生。一个好的prompt还是非常容易在一些prompt交易平台上售出的。之前的ai绘画就有类似的交易平台，比如promptbase,prompthero。上传你的咒语到平台，填写价格等信息即可。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3.制作/代生成AI视频&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;这个分两种用户。一种是尝鲜的用户，可能并不想自己去注册、研究AI视频，所以可以帮忙代生成。另一种则是有定制化需求的用户，需要使用类似Sora这样的工具来生成A!视频素材。当然，这个就依赖于你能否剪辑出一些具有独特风格的短视频内容，在社交平台吸引你的意向客户。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4.使用Sora生成的视频，做个人的自媒体账号&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如果你是一个短视频，或者自媒体从业者。那么你可以优先考虑，直接用Sora生成短视频，然后发布到你的自媒体账号上。国内的平台，比如抖音、快手、视频号、小红书、b站，这些都是流量比较好的平台，尤其是像小红书、视频号这样不那么内卷的平台。比如今天我们有些朋友就已经开始用一些能找到的Sora素材，开始发到视频号上，流量都还不错。 当然，长期来看，如果你想靠自媒体的流量进行转化变现，还是和上一条一样，需要形成自己独特的ai视频风格。比如去年爆火的几类AI视频风格有复活AI名人进行访谈、宠物视频、二次元动漫混剪、电影混剪等等。名人、宠物、大IP都是好的创作方向。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5.上传Sora生成的视频到各大素材网站赚钱&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;国内类似的短视频素材交易网站也有很多。当然，这类的赚钱方式相对来说比较短，平台不可能任由用户上传大量的AI短视频来薅羊毛的。所以，这类方式的行动要快。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6.知识付费，制作Sora相关的使用教程&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;过去一年里，AI领域最经典的好生意就是知识付费，卖课、卖社群。无论你是写一篇Sora的文章，还是一系列的教程，或者一系列的课程视频。这些都是比较好的知识付费产品。光是今天一天，就出了好几篇Sora相关的爆文，有大几百万的人对Sora感兴趣。所以，关于 Sora的知识付费教程，后续是肯定不会缺少用户买单的。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;7.做电商，围绕Sora相关的搜索词做生意&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;很多人容易遗漏掉电商平台用户的精准性。无论是主流的电商平台，比如某宝、某东、某多多,还是类似某鱼、某转这样的平台，A相关的精准付费用户是很多的。所以，任何一个A!相关的需求出现，这些电商平台上就会出现好生意。这些平台上无论是卖工具、卖教程还是卖课程，都非常多。所以，盯紧电商平台关于Sora的关键词，准没有错。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8.做套壳的Sora网站、工具等产品&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;这个对于普通人来说门槛有点高，但还是挺适合一些程序猿、产品经理等互联网从业者的,比如去年AI绘图爆火后，做的比较好的A!绘图产品、导航网站。还有类似妙鸭这种A!写真工具，都吃到了比较大的红利。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;9.用Sora做AI小说推文&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如果说去年的AI小说推文还是集中在AI图片领域，那么今年，AI小说推文很可能直接跨越到A!视频。去年这块爆款很多，赚到钱的也有，但是实际上做过的人都知道花费的精力不少，今年这块很可能还会爆发一次。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;10.直播带货，帮忙推广Sora相关的付费产品&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;对于没有产品制作能力的朋友，其实可以考虑通过直播进行售卖。核心就在于，短视频的生成天然吸引用户的注意力。比如去年很多runway的介绍直播间，流量都很高，但是因为不具备实际的应用场景，所以付费率不高，但是Sora很明显更加成熟。所以，如果你没有做产品的能力，可以考虑帮助别人推广&lt;/p&gt; &lt;h2&gt;学习路径&lt;/h2&gt; &lt;p&gt;学习OpenAl的Sora这样的文本到视频生成技术，可以按照以下几个步骤进行:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1.了解基础知识&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;机器学习与人工智能基础:了解机器学习和人工智能的基本概念，特别是深度学习，因为这些技术是Sora背后的核心。 视频生成和处理基础:了解基本的视频处理技术，包括视频编辑、格式转换等，以及视频生成的基本原理&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.学习具体技术&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;自然语言处理(NLP): Sora依赖于理解和生成文本的能力，因此深入学习NLP是必不可少的 生成对抗网络(GANS)和变分自编码器(VAES):这些是生成图像和视频的关键技术之一，学习它们的原理和应用会非常有帮助。 多模态学习: 了解如何结合文本、图像和视频等不同类型的数据进行学习，因为Sora涉及从文本到视频的转换。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.参加实践项目&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;动手实验:通过实际项目练习技术应用，比如使用TensorFlow或PyTorch等框架进行模型训练。 参与开源项目:加入相关的开源项目，可以让你在实践中学习并贡献自己的力量。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4.了解Sora的具体细节&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;关注OpenAl官方网站和社交媒体渠道，获取Sora的最新动态和官方教程 ·学习Sora的API文档，理解如何调用其功能来生成视频。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5.加入社区&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;加入相关的论坛和社交媒体群组，如Reddit、GitHub或专门的AI论坛，与其他开发者交流学习经验。 参加相关的在线课程和研讨会，不断提升自己的技能和知识，&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6.实际应用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;尝试自己的项目，将Sora应用到实际的场景中去，比如内容创作、游戏开发或其他任何可以想象到的应用&lt;/p&gt; &lt;h2&gt;原理&lt;/h2&gt; &lt;p&gt;Sora是一个扩散模型，它从类似于静态噪声的视频开始，通过多个步骤逐渐去除噪声，视频也从最初的随机像素转化为清晰的图像场景。与GPT模型类似，Sora使用了Transformer架构，有极强的扩展性。&lt;/p&gt; &lt;p&gt;视频和图像是被称为“补丁”的较小数据单位集合，每个“补丁”都类似于GPT中的一个标记(Token)，通过统一的数据表达方式，可以在更广泛的视觉数据上训练和扩散变化，包括不同的时间、分辨率和纵横比。&lt;/p&gt; &lt;p&gt;Sora基于过去对DALL·E和GPT的研究基础构建，利用DALL·E 3的重述提示词技术，为视觉模型训练数据生成高描述性的标注，因此模型能更好的遵循文本指令。&lt;/p&gt; &lt;p&gt;除了能够仅根据文本指令生成视频外，Sora 还能够获取现有的静态图像并从中生成视频，准确且细致地动画化图像内容。模型还可以取一个现有的视频并扩展它或填充缺失的帧。OpenAl 认为，Sora 为能够理解和模拟真实世界的模型尊定了基础，这将是实现 AG! 的一个重要里程碑。&lt;/p&gt; &lt;h2&gt;应用场景&lt;/h2&gt; &lt;p&gt;Sora作为一种文本到视频的生成技术，其潜在的应用场景非常广泛，可以革新多个行业和领域。以下是一些可能的应用场景:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1.内容创作&lt;/strong&gt; :对于视频制作人、动画师和内容创作者来说，Sora可以快速将脚本或故事概念转化为视觉内容，大幅度提高内容生产的效率和创意表达的可能性。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2.教育与培训&lt;/strong&gt; :在教育领域，Sora可以用来创建教学视频或模拟情景，使学习材料更加生动、直观，提高学习的兴趣和效果。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3.广告与营销&lt;/strong&gt; :企业可以利用Sora快速生成针对不同市场和客户群体的定制化视频广告，提高营销活动的响应速度和个性化水平。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4.游戏与娱乐&lt;/strong&gt; :Sora可以用于生成游戏内的动态场景、角色互动或者故事情节，为游戏开发和电影制作提供新的创意工具。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5.新闻与媒体&lt;/strong&gt; :新闻机构可以使用Sora，根据报道的文本内容快速创建视觉支持材料，使新闻报道更加生动、吸引人。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6.虚拟现实(VR)和增强现实(AR)&lt;/strong&gt; :Sora可以为VR和AR应用创建丰富的视觉内容，提供更加沉浸式的用户体验.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;7.个性化视频生成&lt;/strong&gt; :根据用户的兴趣和偏好，自动创建个性化视频内容，如个性化旅行指南、学习材料或健康指导视频。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8.艺术创作&lt;/strong&gt; :艺术家和设计师可以使用Sora探索新的视觉表达方式，创作独一无二的数字艺术作品。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;9.自动视频摘要&lt;/strong&gt; :对于长视频内容，如会议、讲座或教程，Sora可以自动生成视频摘要，帮助用户快速获取关键信息&lt;/p&gt; &lt;p&gt;Sora的应用潜力取决于其技术的成熟度、生成视频的质量以及用户界面的友好度。随着技术的不断进步和创新，未来Sora的应用场景将进一步扩展，为各行各业提供更多的可能性和机遇。&lt;/p&gt; &lt;h2&gt;局限性&lt;/h2&gt; &lt;p&gt;Sora的局限性在于，它可能难以准确模拟复杂场景的物理原理，并且可能无法理解因果关系。例如，在文本描述为“五只灰狼幼崽在一条偏僻的碎石路上互相嬉戏、追逐”的画面中，狼的数量会凭空出现或消失。此外，Sora还可能会混淆空间细节，并在精确描述时间方面遇到困难，&lt;/p&gt; &lt;h2&gt;对行业的影响&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;1.没有演员的影视作品出现，对演员来说是个小挑战，将出现真正的“虚拟偶像”，此前的二次元人物并没有真正达到“偶像”的级别。&lt;/li&gt; &lt;li&gt;2.利好编剧行业，剧本、文本创作力成为核心竞争力。&lt;/li&gt; &lt;li&gt;3.Sora可能才是真正的文生视频，此前的文生视频大多只有2秒，仅仅是对象的小幅度移动,&lt;/li&gt; &lt;li&gt;4.openAl继续拉大领先程度，对众多还在进行大模型测试打分pk的厂商，构成压力。&lt;/li&gt; &lt;li&gt;5.直接的影响是影视行业，特别是特效行业。使用Al来制作一些特效和高风险的镜头，可以大幅降低拍摄成本，也可以避免很多危险&lt;/li&gt; &lt;li&gt;6.摄影师行业也会受到影响，用文本来生成一些视频，可以省去很多拍摄工作。&lt;/li&gt; &lt;li&gt;7.短视频流行开以后，视频剪辑师也随之成为一个热门职业。如果视频剪辑的工作可以用AI来代替，可能会有很多视频剪辑师失业。&lt;/li&gt; &lt;li&gt;8.对于很多短视频创作者来说，用AI来替代繁琐的剪辑工作，可以大幅提高工作效率。&lt;/li&gt; &lt;li&gt;9.很多歌手拍摄MV都是大成本制作，如果可以用A|来生成所需要的MV画面，也可以省去很大一部分制作成本.。&lt;/li&gt; &lt;li&gt;10.另外，如果真正意义上的文生视频得以实现，可能会有不法份子利用这项技术实施新手段的违法犯罪。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Sora，作为一个文本到视频的生成工具，拥有广泛的未来发展前景，尤其在提升创作效率、内容定制化、以及跨媒体应用等方面。&lt;/p&gt; &lt;p&gt;随着技术的发展和应用场景的不断拓展，Sora的未来发展将可能重塑多个行业的内容创作和分发方式，提供前所未有的创作自由度和个性化体验。&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 26 Feb 2024 02:01:00 GMT</pubDate>
    </item>
    <item>
      <title>文件上传测试用例</title>
      <link>https://maruifu.cn/article/294</link>
      <content:encoded>&lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;测试案例名称&lt;/th&gt;&lt;th&gt;测试案例描述&lt;/th&gt;&lt;th&gt;步骤描述&lt;/th&gt;&lt;th&gt;预期结果&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;附件上传-文件命名检查-符合文件命名规范&lt;/td&gt;&lt;td&gt;检查符合文件命名规范的文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，构造文件命名规范、命名长度、格式和大小都符合条件的文件，进行上传。(文件命名规范可以参考需求要求)&lt;/td&gt;&lt;td&gt;文件上传成功&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;附件上传-文件命名检查-不符合文件命名规范&lt;/td&gt;&lt;td&gt;检查不符合文件命名规范的文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，构造命名长度、格式和大小都符合条件，但是命名不符合规范的文件，进行上传。(文件命名规范可以参考需求要求)&lt;/td&gt;&lt;td&gt;文件上传失败&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;附件上传-文件命名检查-符合文件长度规范&lt;/td&gt;&lt;td&gt;查符合文件长度规范的文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，构造文件命名规范、命名长度、格式和大小都符合条件的文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;这个案例可以和“附件上传-文件命名检查-符合文件长度规范”合并执行检香&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;附件上传-文件命名检查-不符合文件长度规范&lt;/td&gt;&lt;td&gt;检查不符合文件长度规范的文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，构造命名规范、格式和大小都符合条件，但是长度不符合规范的文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-文件路径可手动输入-输入正确路径&lt;/td&gt;&lt;td&gt;如果文件上传路径可以手动修改的话，输入正确的路径，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查文件路径是否可以手动输入，如果可以手动输入，选择合法的文件，输入正确的路径，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-文件路径可手动输入-输入错误路径&lt;/td&gt;&lt;td&gt;如果文件上传路径可以手动修改的话，输入错误的路径，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查文件路径是否可以手动输入，如果可以手动输入，输入错误的路径，进行上传。 (错误路径包含以下常见情况: 1、路径格式存在问题，无法解析，2、路径格式正确，但是路径下找不到指定的文件:3、相对路径和绝对路径问题。)&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-文件路径不可手动输入-正常操作&lt;/td&gt;&lt;td&gt;如果文件上传路径不可以手动修改的话，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查文件路径是否可以手动输入，如果不可以手动输入，选择合法的文件，检查自动带出文件路径，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-文件路径不可手动输入-篡改路径为正确路径&lt;/td&gt;&lt;td&gt;如果文件上传路径不可以手动修改的话，篡改路径为正确路径，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查文件路径是否可以手动输入，如果不可以手动输入，通过F12开发者工具，篡改文件路径为正确的路径地址，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-修改选择但未上传的文件&lt;/td&gt;&lt;td&gt;如果选择了一个文件，但是未上传，重新选择一个文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查文件路径是否可以手动输入，如果不可以手动输入，通过F12开发者工具，篡改文件路径为错误的路径地址，进行上传(错误路径包含以下常见情况: 1、路径格式存在问题，无法解析:2、路径格式正确，但是路径下找不到指定的文件，3、相对路径和绝对路径问题。)&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-修改选择但未上传的文件&lt;/td&gt;&lt;td&gt;如果选择了一个文件，但是未上传，重新选择一个文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择文件A，如果有文件路径的话，检查能够正常带出文件A的路径，不做上传，重新选择文件B，检查文件路径更新为文件B的路径，重新上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;附件上传-文件路径检查-选择一个打开的文件进行上传&lt;/td&gt;&lt;td&gt;选择一个打开的文件，进行上并检查文件能否正学上传&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个已经打开的合法文件，进行上传&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;附件上传-文件类型检查-符合文件类型类型命名全部大写&lt;/td&gt;&lt;td&gt;如果文件类型符合规范，类型命名全部为大写，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择符合文件类型的文件,将文件类型后缀名全部修改为大写，进行上传。如果支持多种文件类型的话，需要覆盖所有文件类型。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;这个是开发经常容易犯的一个错误，文件类型根据大写或者小写的类型来判断，实际上文件类型命名是无需区分大小写的。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;附件上传-文件类型检查-符合文件类型类型命名全部小写&lt;/td&gt;&lt;td&gt;如果文件类型符合规范，类型命名全部为小写，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择符合文件类型的文件,将文件类型后缀名全部修改为小写，进行上传。如果支持多种文件类型的话，需要覆盖所有文件类型。&lt;/td&gt;&lt;td&gt;文件上传成功&lt;/td&gt;&lt;td&gt;这个是开发经常容易犯的一个错误，文件类型根据大写或者小写的类型来判断，实际上文件类型命名是无需区分大小写的。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;14&lt;/td&gt;&lt;td&gt;附件上传-文件类型检查-符合文件类型类型命名大小写混合&lt;/td&gt;&lt;td&gt;如果文件类型符合规范，类型命名大小写混合，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择符合文件类型的文件将文件类型后缀名修改为大小写混合的方式，进行上传。如果支持多种文件类型的话，需要覆盖所有文件类型。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;这个是开发经常容易犯的一个错误，文件类型根据大写或者小写的类型来判断，实际上文件类型命名是无需区分大小写的。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;附件上传-文件类型检查-不符合文件类型&lt;/td&gt;&lt;td&gt;如果文件类型不符合规范，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择不符合文件类型的文件进行上传。可以选择不符合文件类型的其他多种文件类型进行验证。&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;为了支持文件类型的可扩展性，建议可以将文件类型做成配置项，灵活支持。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-空文件上传&lt;/td&gt;&lt;td&gt;如果文件内容为空，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个空文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-文件大小略小于限制大小上传&lt;/td&gt;&lt;td&gt;如果文件上传大小路小于限制大小，进行上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，如果上传文件有大小限制选择一个文件大小略小于限制大小的文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-文件大小等于限制大小上传&lt;/td&gt;&lt;td&gt;如果文件上传大小等于限制大小，进行上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，如果上传文件有大小限制选择一个文件大小等于限制大小的文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-文件大小略大于限制大小上传&lt;/td&gt;&lt;td&gt;如果文件上传大小大于限制大小，进行上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，如果上传文件有大小限制选择一个文件大小略大于限制大小的文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-上传文件大小超过存储剩余空间&lt;/td&gt;&lt;td&gt;如果文件大小大于存储剩余空间，进行上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，先通过压力测试将存储剩余空间压缩到很小的一个值 (实际操作可以先压满再删除少量文件)，然后选择一个文件，大小超过存储剩余空间的，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败&lt;/td&gt;&lt;td&gt;生产对存烤的使用都是有监控的，当达到一定警戒值时会做扩容或者文件清理，所以生产一般不会出现这种情况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-上传文件时存储空间已满&lt;/td&gt;&lt;td&gt;如果存储空间已满，进行文任上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，先通过压力测试将存储空间全部占满，再选择一个合法文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败&lt;/td&gt;&lt;td&gt;生产上对存储的使用都是有监控的，当达到一定警戒值时会做扩容或者文件清理，所以生产一般不会出现这种情况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查没有限制大小时上传正常大小文件&lt;/td&gt;&lt;td&gt;如果文件上传大小没有做限制，上传一个正常大小文件检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，如果上传文件没有大小限制，选择一个正常大小文件，进行上传。 (正常大 小的范围可以根据应用场景来判断)&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;23&lt;/td&gt;&lt;td&gt;附件上传-文件大小检查-没有限制大小时上传超大文件&lt;/td&gt;&lt;td&gt;如果文件上传大小没有做限制，上传一个超大文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，如果上传文件没有大小限制，选择一个超大文件，进行上传。 (可以根据应用场景判断正常文件大小，再将大小按照一定数量级扩大)&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;附件上传-文件内容检查-同名文件上传&lt;/td&gt;&lt;td&gt;如果存在已上传的同名文件再次上传，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个合法文件进行上传，上传成功后，再选择该文件，进行上传。&lt;/td&gt;&lt;td&gt;文件能否上传成功，需根据需求和系统实现来定。如果系统不允许上传同名文件的，则文件上传失败，给出对应提示。如果系统允许同名文件上传，需要看上传规则是什么样的。有些是覆盖上传，则需要用同名不同内容的文件上传，检查上传后的文件内容是否为最新的，有些是上传时会做重命名保存，同名的文件上传到存储上是做为不同文件保存。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;25&lt;/td&gt;&lt;td&gt;附件上传-文件内容检查-合法文件上传&lt;/td&gt;&lt;td&gt;如果上传文件需要校验文件内容的话，上传一个符合校验规则的合法文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个符合文件内容检查规则的合法文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;附件上传-文件内容检查-非法文件上传&lt;/td&gt;&lt;td&gt;如果上传文件需要校验文件内容的话，上传一个不符合校验规则的合法文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个不符合文件内容检查规则的非法文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;27&lt;/td&gt;&lt;td&gt;附件上传-文件内容检查-病毒文件上传&lt;/td&gt;&lt;td&gt;构造一个病毒文件，检查文件能否正常上传。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，构造一个病毒文件，进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败。(说明: 如果需要做附件上传功能，一般更要引用病毒扫描系统，在文件上传前先进行病毒扫描）&lt;/td&gt;&lt;td&gt;这种情况在公司内网很难模拟测试，一般构造病毒软件后，就被公司标装的杀毒软件识别和删除了。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;28&lt;/td&gt;&lt;td&gt;附件上传-上传响应时间检查&lt;/td&gt;&lt;td&gt;检查文件上传的响应时间是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择一个大小等于限制大小的文件进行上传，检查上传响应时间是否正常。&lt;/td&gt;&lt;td&gt;响应时间应该在合理范围内。(合理范围内指符合用户需求说明，或者用户感受良好，响应时间在接受范围内)如果当响应时间超过一定时间，应该给出提示&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;附件上传-上传页面显示和控制检查&lt;/td&gt;&lt;td&gt;检查文件上传页面的页面显示和控制是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查上传页面的显示和控制是否正常。如按钮文字显示正确性、说明性文字的正确性、显示风格是否和其他页面一致、必填项&amp;amp;非必填项的显示和控制、选择和上传文件的控制等&lt;/td&gt;&lt;td&gt;页面显示和控制正常。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;附件上传-上传成功提示信息检查&lt;/td&gt;&lt;td&gt;检查文件上传成功后，提示信息是否正常、合理。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。&lt;/td&gt;&lt;td&gt;文件上传成功，且成功后应该给出合理的提示信息。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;31&lt;/td&gt;&lt;td&gt;附件上传-上传失败提示信息检查&lt;/td&gt;&lt;td&gt;检查文件上传失败后，提示信息是否正常、合理。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择非法文件进行上传。&lt;/td&gt;&lt;td&gt;文件上传失败,且失败后应该给出合理的提示信息。校验的报错信息要求以通俗易懂的文字信息展示，而不是抛出报错代码信息。&lt;/td&gt;&lt;td&gt;一般建议在文件提交到服务器处理前做文件命名大度、大小、类型等的合法性校验，校验失败的话给出提示，不再做后续处理，校验成功后才会提交服务器处理。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;附件上传-上传页面可用性检查&lt;/td&gt;&lt;td&gt;检查文件上传页面的页面可用性是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，检查页面可用性。如页面是否美观、是否易用 (键盘和鼠标的操作、tab键的跳转) 等。&lt;/td&gt;&lt;td&gt;用户可用性较好。&lt;/td&gt;&lt;td&gt;用户可用性没有明显的需求和测试标准。一般以正常的操作和感受为标准，违反人类正常操作习惯和审美的，都可以认为是可用性不佳。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;33&lt;/td&gt;&lt;td&gt;附件上传-上传后-访问或者下载检查&lt;/td&gt;&lt;td&gt;检查文件上传后，是否有方法可以访问上传的文件，如果可以的话，检查文件访问或者下载是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。如果系统有方法可以访问上传后的文件，进行访问或者下载检查。&lt;/td&gt;&lt;td&gt;可以正常打开或者下载文件，目文件内容和上传时完全一致。&lt;/td&gt;&lt;td&gt;有些是系统本身不支持上传的文件的访问和下载。可以通过存储上的文件进行验证。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;附件上传-上传后-访问安全性检查&lt;/td&gt;&lt;td&gt;检文件上传后，是否有方法可以访问上传的文件，如果可以的话，检查非授权用户能否访问上传文件。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。如果系统有访问可以访问上传后的文件，选择非授权用户进行访问或者下载。 (权限控制需要根据系统来确定，有些是针对用户的，有些是针对角色的)&lt;/td&gt;&lt;td&gt;非授权用户无法进行访问和下载&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;35&lt;/td&gt;&lt;td&gt;附件上传-上传后-数据库检查&lt;/td&gt;&lt;td&gt;如果文件上传后，有将相关信息写入数据库，需要检查数据库记录内容是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。如果文件上传相关信息有写入数据库，需要检查数据库记录的内容是否正确。&lt;/td&gt;&lt;td&gt;文件上传写入数据库的相关信息正确。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;附件上传-上传后-文件处理检查&lt;/td&gt;&lt;td&gt;如果系统对于上传的文件内容还有做处理的话，需检查处理功能是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。如果系统有对上传的文件进行了处理，如写入数据库、触发一些任务处理等，需要检查文件处理过程是否正常。&lt;/td&gt;&lt;td&gt;文件上传后，处理过程正常。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;附件上传-上传后-删除验证&lt;/td&gt;&lt;td&gt;如果文件上传后，可以删除需要检查文件上传功能是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，选择合法文件进行上传。如果系统支持文件删除，选择上传的文件，进行删除。&lt;/td&gt;&lt;td&gt;页面提示删除成功。数据库中记录被物理删除或者逻辑删除。存储上的文件是否需要删除根据需求来确定。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;38&lt;/td&gt;&lt;td&gt;附件上传-压力测试-批量上传检查&lt;/td&gt;&lt;td&gt;进行文件批量上传的压力测试，检查性能和上传功能是否正常。&lt;/td&gt;&lt;td&gt;在附件上传功能模块，进行附件上传的压力测试压力测试重点关注系统的性能和上传功能本身是否正常。&lt;/td&gt;&lt;td&gt;系统性能文件上传功能都正常。性能可以检查服务器CPU、内存、后台日志。文件上传可以通过数据库、存储来检查。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;转载于 &lt;a href="https://wenku.baidu.com/view/c676bde6d6bbfd0a79563c1ec5da50e2524dd184?pcf=2&amp;amp;fr=aladdin267&amp;amp;bfetype=new&amp;amp;_wkts_=1706515238288&amp;amp;bdQuery=%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B&amp;amp;bfetype=new" target="_blank"&gt;https://wenku.baidu.com 点我查看&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 24 Jan 2024 13:17:00 GMT</pubDate>
    </item>
    <item>
      <title>AI大模型</title>
      <link>https://maruifu.cn/article/293</link>
      <content:encoded>&lt;h1&gt;&lt;strong&gt;什么是AI大模型？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;strong&gt;我们在生活中常常使用过很多模型，比如自制雪糕的雪糕模具，蛋糕店里摆着的蛋糕模型这些都是模具，装着鸡蛋羹的碗等等，我们可以使用这些模具来更加简单的完成最终的成品。这些是实体店模型，映射到虚拟上，就包括我们听到过很多次的数据建模；&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;举例：我需要大量计算一个数的平方最后减去3，一个一个计算太麻烦了，我就可以先使用一个数，计算它的平方再减去3。根据这个例子，建立一个模型，就变成了一个虚拟的“模具”，我就可以使用这个“模具”来计算我其他的数据了。其中，数的平方减去3，就是这个模型的算法；把这个算式封装好的盒子，就是模型，是不是特别好理解。我们可以使用这个模型来增加提高我们的工作效率。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/output.png" alt="output" title="output" /&gt;&lt;/p&gt; &lt;p&gt;大模型是什么? 大模型技术是一种基于深度学习的人工智能技术，它通过训练大规模的神经网络来生成具有特定功能的模型。大模型技术被广泛应用于自然语言处理、图像识别、语音识别、预测模型等领域。&lt;/p&gt; &lt;p&gt;大模型时代 从去年底开始，大模型的发展趋势就非常迅猛，代表着人工智能进入了一个新的时代 国内在大模型领域的发展中，再次取得了令人瞩目的成就。不仅在技术上有所突破，而且在实际应用中，也展现出了巨大的潜力。这也反映出，大众对于大模型的关注度和热情正在不断高涨 除国家在大模型方向大力投入之外，国内多家科技公司也纷纷推出了自己的人工智能大模型产品。例如文心一言、通义千问、讯飞星火、天工、ChatGLM、MOSS、baichuan,可谓层出不穷&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122083824188.png" alt="image-20240122083824188" title="image-20240122083824188" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122083908196.png" alt="image-20240122083908196" title="image-20240122083908196" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122083933838.png" alt="image-20240122083933838" title="image-20240122083933838" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;Ai大模型就是一种很聪明的电脑程序，它可以学习很多很多的信息，比如文字、图片、声音，然后根据这些信息来做各种各样的事情，比如写文章、画画、聊天等。AI大模型就像是一个超级大脑，可以处理很多很复杂的问题，也可以适应不同的场景和需求。AI大模型是人工智能的一种重要形式，也是人类探索智能的一种方式。简单来说，就是在大数据的支持下进行训练，学习出一些特征和规则，微调后应用在各场景任务中。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;AI大模型具有很高的计算和存储需求，需要使用极为强大的计算设备和高效的算法才能训练和应用，所以参数量一般可以达到惊人的数十亿或者数千亿。例如OpenAI的GPT系列，最开始的GPT-1拥有1.17亿个参数，到GPT-3的参数已经到达1750亿个，最新的GPT-4没有给出具体的参数量，但根据推测，它或将接近万亿&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122084007825.png" alt="image-20240122084007825" title="image-20240122084007825" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;传统的小模型生成模式相比，大模型能够大幅缩减特定模型训练所需要的算力和数据量，缩短模型的开发周期，并得到更好的模型训练效果。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;可以说，大模型的真正意义在于改变了 AI 模型的开发模式，将模型的生产由“作坊式”升级为“流水线”。而模型开发模式的转变，使得 AI 技术在落地时拥有更强的通用性，可以泛化到多种应用场景。由此利用大模型的通用能力可以有效应对多样化、碎片化的AI应用需求，为实现规模推广AI落地应用提供可能&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h1&gt;&lt;strong&gt;AI和AI大模型区别&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;**👉AI（人工智能）**&lt;strong&gt;是指模拟、复制、扩展人类智能的科学与工程领域。它是通过机器学习、深度学习、自然语言处理等技术，使机器能够模拟和执行人类智能活动的分支。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;👉AI大模型****是一种具有巨大参数量的深度神经网络模型。这些模型通常由数十亿、甚至上百亿个参数组成，可以在大规模数据集上进行训练。AI大模型的典型代表是GPT-3（Generative Pre-trained Transformer-3），它是由OpenAI开发的自然语言处理模型，拥有1750亿个参数&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;大模型门槛高不高？&lt;/h1&gt; &lt;p&gt;大模型门槛高不高，要分三种情况&lt;/p&gt; &lt;p&gt;大模型领域当中不同类型的玩家，&lt;/p&gt; &lt;p&gt;👉第一种：像谷歌 微软 百度 open ai 这样的玩家，从0开始做一个这样的基础大模型&lt;/p&gt; &lt;p&gt;1、这一部分无论是数据还是算力，还是人才的要求都非常高。整个的大模型，他之所以有这样的一个通用的智能化的能力，实际上是它经过了海量的数据的训练，（可能相当于数百万个这样的人，他们一生的阅读量）&lt;/p&gt; &lt;p&gt;2、另一方面实际上是这种高质量的标注数据，比如说像OpenAI，他们有大量的这种专业领域的标注员，那么除了数据之外，当然算力的要求也是非常高的，像Chat GPT这样的一个千亿级别的大模型要连续训练1000张A100连续训练100天这样的一个时间，所以这个成本也非常高（动辄花几千万美金 甚至几亿 几十亿美金购买GPU）&lt;/p&gt; &lt;p&gt;【自建基础大模型，算力、算法、数据，人才要求高】&lt;/p&gt; &lt;p&gt;👉第二种：在已有开源的基础大模型之上，比如chatglm3、 llama2、 阿里通义这样的基础大模型之上，做这种行业的领域大模型，就好比站在巨人的肩膀之上，训练完成之后90%的工作大模型都给你解决了，它所需要的数据量、它所需要的计算量、成本方面就算我们是普通程序员也是可以接受。&lt;/p&gt; &lt;p&gt;电商 量化交易 教育 工业的知识库 天气预测 客服机器人等  都是可以落地的，现在市面招聘99%也都是领域大模型的产品经理 、算法工程师&lt;/p&gt; &lt;p&gt;【行业大模型，基于基础大模型微调，针对性定制】&lt;/p&gt; &lt;p&gt;👉第三种：基于前两类的大模型，在上面开发应用，门槛降的非常低，整个的创新的速度非常快（在进行模型二次训练的时候顺手就搞定了）&lt;/p&gt; &lt;p&gt;【用大模型开发应用，通过开发工具，降低门槛】&lt;/p&gt; &lt;p&gt;大模型本身已经是非常强大了，但是无法满足我们工作需求。所以我们做的是基于基座大模型做模型二次训练 做独有场景 独有数据方向 私有化部署（真正意义上的几何倍率的提高工作效率，成为超级个体，成为大模型全栈工程师）&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;AI大模型应用案例&lt;/strong&gt;&lt;/h1&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;应用案例&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;自然语言处理&lt;/td&gt;&lt;td&gt;AI大模型在自然语言处理领域有着广泛应用。例如 ，GPT-3模型可以进 行智能文本生成 ，根据输入的提示生成连贯、有逻辑的文章、对话或 代码等。此外 ，BERT模型可以进行文本分类、实体识别、情感分析等 任务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;机器翻译&lt;/td&gt;&lt;td&gt;AI大模型在机器翻译领域也有广泛应用。例如 ，T5模型可以实现多语 言之间的翻译 ，不仅可以进行常见语种之间的翻译 ，还可以进行稀缺 语种的翻译 ，提供更加便捷的跨语言交流服务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;图像识别&lt;/td&gt;&lt;td&gt;AI大模型在图像识别领域具有出色的表现。例如 ，GPT-3模型可以进行 图像描述生成 ，根据输入的图像生成与图像内容相符的文字描述。此 外 ，ViT模型可以实现图像分类、目标检测、图像生成等任务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;语音识别&lt;/td&gt;&lt;td&gt;AI大模型在语音识别领域也有重要应用。例如 ，DeepSpeech模型可 以将语音信号转换为文字 ，实现语音转写和语音命令识别等功能。该模型在语音识别任务上取得了很高的准确率。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;推荐系统&lt;/td&gt;&lt;td&gt;AI大模型在推荐系统中起到了关键作用。例如 ，GPT-3模型可以根据用 户的历史行为和推荐算法生成个性化的推荐结果 ，提供更加精准的推 荐服务。此外 ，DALL -E模型可以根据用户的需求生成符合用户偏好的 图片 ，提供个性化的视觉推荐。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;医疗诊断&lt;/td&gt;&lt;td&gt;AI大模型在医疗诊断领域也有重要应用。例如 ，DeepMind团队开发的 AlphaFold模型可以预测蛋白质的三维结构 ，帮助科研人员理解蛋白 质的功能和疾病机制。此外 ，AI大模型还可以通过分析医疗影像数据 进行疾病诊断和辅助决策。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;智能客服&lt;/td&gt;&lt;td&gt;AI大模型在智能客服领域有着广泛应用。例如 ，GPT-3模型可以根据用 户的问题生成智能回复 ，提供个性化的客服服务。该模型可以帮助企 业提高客户满意度和运营效率&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;智能物流&lt;/td&gt;&lt;td&gt;AI大模型在物流领域有着广泛应用。例如 ，GPT-3模型可以根据订单信息和物流数据进行路径规划和运输优化 ，提高物流效率和准时率。此外 ，AI大模型还可以通过分析大数据进行货物跟踪和库存管理 ，提供更加智能化的物流解决方案。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;...&lt;/td&gt;&lt;td&gt;当然并不是只有这些的哈，还有很多的应用场景在等待着我们的开发。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h1&gt;&lt;strong&gt;大模型的应用可以在以下领域：&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122084703646.png" alt="image-20240122084703646" title="image-20240122084703646" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;当然并不是只有这些的哈，还有很多的应用场景在等待着我们的开发。&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h1&gt;&lt;strong&gt;大模型技术为什么会这么多公司追捧呢？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;☘️ &lt;strong&gt;核心就是两点 ：效率高、想象力巨大。&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;h2&gt;&lt;strong&gt;效率高的事情&lt;/strong&gt;&lt;/h2&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;开发的ChatALL.ai大模型全球github热榜第一，日本，美国专家都在评论 研究&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;过程中前端 后端 测试 产品 设计图片 UI都是利用大模型半个月开发完毕，正常效率是4 5个工程师 几个月的工作量。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;他的能力 咱们也是可以做到的💪&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;h2&gt;&lt;strong&gt;想象力大事情怎么解释呢？&lt;/strong&gt;&lt;/h2&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;说两个方向： 存量市场和增量市场&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1：存量市场逻辑：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;​     &lt;strong&gt;2018年开始，移动互联网增速趋向于饱和，像我们平时使用的“抖音”“快手”“微信”支付宝““携程”“小红书”等等，基本就没变过样子，企业招开发岗 更多是维持本身业务的发展，而当企业的商业增速 流量增速到达瓶颈后，本身开发工程师是不带来新的经济增长的 自然配合此业务的“产品经理”“项目经理”等相关岗位和开发工程师一样 就会薪资下降  ，薪资水平刚刚维系企业可以正常运转即可，或者直接裁员其他冗余岗位，这就是”存量市场逻辑“&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2：增量市场逻辑：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;​    &lt;strong&gt;自从chatgpt火起来之后，各个大厂开始自研基座大模型，很多文案 编辑 开发等工作大模型都可以替代 或替代大部分，而它真正的想象力在于，很多微小的 或传统的行业 都可以利用大模型重塑一次，真正改变生产力，比如“量化交易”“医疗问诊”“客服机器人”“教育运营”“企业CRM“”线上企业框架搭建“等等等，有更多新的场景亟待解决，这就是机会，而这些新的机会，是给企业降本增效 或带来新的经济增长的，自然薪资就高 而且给钱还痛快，这就是”增量市场逻辑“&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;👇在这样的市场情况下，大模型一定是未来重塑生产力的方式，薪资也非常高，那么我们应该如何学习呢？ 目前个人认为有三种情况&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、有特定的场景 或者公司需求 急待解决，想要训练专业领域的大模型&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2、本身从事IT工作，自己不知道大模型可以做些什么，简单提升下工作效率，或闲余时间可以利用大模型做一些副业&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;认可大模型方向，想要ALL IN进去 快速转行&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;大模型出来后，您也知道，对于行业的冲击还是很强的&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;学习AI大模型可以具体帮咱们实现：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;①✅基于大模型底层技术，让咱们前端 后端 数据分析 产品  绘图 算法工程等一个人可以做。自身工作起码提效50%&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;②✅学会模型精调，掌握独立训练基础大模型，并且某个专业领域落地部署的能力&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;③✅AI新方向薪资高，打破了行业壁垒，不像传统IT，学大模型薪资至少上涨10%~15%，且拥有绝佳的高薪岗位&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;④✅技术傍身&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;写代码？ 编程要求很高？  不存在的同学，大模型现在很强，核心不是代码基础  是编程思维，它并不是只停留在一个gpt上，大模型本质是重塑生产力，&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;学模型精调、做AI全栈、想做出自己的私人场景、私人数据的模型产品，&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;想就业、想转行、想创业、想提效、想运用AI、想有个副业，咱们课程一定都是巨合适的****以上的点都是是本次课程能给你带来的收获和帮助，所以按时上课，及时了解，及时跟进学习&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;大模型AI技术应用的四个层次&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;我们可以看下自己处于第几层的哦&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122084811543.png" alt="image-20240122084811543" title="image-20240122084811543" /&gt;&lt;/p&gt; &lt;h1&gt;&lt;strong&gt;什么是AI大模型全栈工程师？&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2024/01/22/image-20240122084839198.png" alt="image-20240122084839198" title="image-20240122084839198" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 22 Jan 2024 00:49:00 GMT</pubDate>
    </item>
    <item>
      <title>mac版无法备份iphone 提示空间不足</title>
      <link>https://maruifu.cn/article/292</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;查看空间明明有三百多G，备份256G的手机提示空间不足&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;手动清理 Time Machine 本地快照&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 列出你最近的备份 sudo tmutil listlocalsnapshots / # 删除指定的快照 # tmutil deletelocalsnapshots 认证码 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# 具体如下： maruifu@MaRuifudeiMac ~ % sudo tmutil listlocalsnapshots / Password: Snapshots for volume group containing disk /: com.apple.TimeMachine.2023-09-08-233944.local com.apple.TimeMachine.2023-10-13-202333.local com.apple.TimeMachine.2023-10-13-214400.local com.apple.TimeMachine.2023-10-13-220956.local maruifu@MaRuifudeiMac ~ % tmutil deletelocalsnapshots 2023-09-08-233944 Deleted local snapshot '2023-09-08-233944' &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;来源: &lt;a href="https://link.zhihu.com/?target=https%3A//forums.macrumors.com/threads/solution-reclaim-storage-back-from-system.2073174/" target="_blank"&gt;Solution: Reclaim storage back from &amp;quot;System&amp;quot;&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Fri, 13 Oct 2023 16:30:22 GMT</pubDate>
    </item>
    <item>
      <title>使用 Nexus3 Repository Manager 搭建 npm 私服</title>
      <link>https://maruifu.cn/article/291</link>
      <content:encoded>&lt;h2&gt;下载安装&lt;/h2&gt; &lt;p&gt;在官网下载 &lt;a href="https://www.sonatype.com/download-oss-sonatype" target="_blank"&gt;Nexus Repository Manager OSS 3.x&lt;/a&gt;, 解压至任意位置. 管理员运行cmd, 切换到 nexus-3.58.1-02/bin 目录&lt;/p&gt; &lt;pre&gt;&lt;code&gt; ./nexus start &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;第一次要耐心等待一会，等待启动完毕后,&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;浏览器访问http://127.0.0.1:8081, 点击右上角 Sign In 登陆, 默认账号: admin 密码: admin123&lt;/p&gt; &lt;h2&gt;添加npm仓库&lt;/h2&gt; &lt;p&gt;点击左侧菜单&lt;code&gt;Repositories&lt;/code&gt;,点击 &lt;code&gt;Create repository&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008094238110.png" alt="image-20231008094238110" title="image-20231008094238110" /&gt;&lt;/p&gt; &lt;p&gt;看到仓库类型列表&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008094422165.png" alt="image-20231008094422165" title="image-20231008094422165" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;npm(group)表示分组 npm(hosted)表示本机私有 npm(proxy)表示远程代理。&lt;/p&gt; &lt;p&gt;若registry配置为group(包括hosted和proxy)，首先会从hosted取，若无则从proxy取并缓存，下次则会从缓存取。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;添加npm(proxy)&lt;/h3&gt; &lt;p&gt;输入 Name: npm-proxy, remote storage 填写  https://registry.npm.taobao.org 或  https://registry.npmjs.org. 用于将包情求代理到地址地址&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008100046696.png" alt="image-20231008100046696" title="image-20231008100046696" /&gt;&lt;/p&gt; &lt;p&gt;最后点击下方的 &lt;code&gt;Create repository&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;添加npm(hosted)&lt;/h3&gt; &lt;p&gt;再次点击Create repository按钮,增加npm(hosted)输入 Name: npm-hosted, 点击下方的 &lt;code&gt;Create repository&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;添加npm(group)&lt;/h3&gt; &lt;p&gt;再次点击Create repository按钮,增加npm(group)输入 Name: npm-group,Member repositories里选择之前添加的2个移动右边&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008100536191.png" alt="image-20231008100536191" title="image-20231008100536191" /&gt;&lt;/p&gt; &lt;h2&gt;配置与验证npm仓库&lt;/h2&gt; &lt;p&gt;查看并设置nodejs的默认仓库地址&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npm config get registry  #http://registry.cnpmjs.org/ npm config set registry http://x.x.x.x:8081/repository/npm-group/ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;验证仓库地址&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#随便进入一个目录 初始化package npm init -y # 查看是否从自己的仓库地址拉取包 npm --loglevel info install jquery  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008101249274.png" alt="image-20231008101249274" title="image-20231008101249274" /&gt;&lt;/p&gt; &lt;p&gt;从上图中可以看到，fetch地址即为私服地址。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;查看刚搭建的私服里的内容为空, 在安装了依赖包后，就会有一些被缓存了，下次请求就不会走外网了&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;发布包到私服&lt;/h2&gt; &lt;p&gt;npm发布包是需要先登录的，默认是登录到npm官方服务器，若registry已更改为其它地址则可能登录失败，而这里我们只是想把包发布到自己私有的服务器上。&lt;/p&gt; &lt;h3&gt;添加权限认证&lt;/h3&gt; &lt;p&gt;设置权限, Realms 菜单, 将 npm Bearer Token Realm 添加到右边&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008101807669.png" alt="image-20231008101807669" title="image-20231008101807669" /&gt;&lt;/p&gt; &lt;h3&gt;创建角色&lt;/h3&gt; &lt;p&gt;创建nx-deploy角色 给角色赋于一个&lt;code&gt;nx-repository-view-*-*-*&lt;/code&gt;权限&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008101953880.png" alt="image-20231008101953880" title="image-20231008101953880" /&gt;&lt;/p&gt; &lt;h3&gt;创建用户&lt;/h3&gt; &lt;p&gt;创建deployer 用户,密码也为 deployer,同时设定角色为nx-deploy&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/08/image-20231008102236283.png" alt="image-20231008102236283" title="image-20231008102236283" /&gt;&lt;/p&gt; &lt;h3&gt;客户端的.npmrc配置&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;registry=http://x.x.x.x:8081/repository/npm-group/ email=email@maruifu.cn always-auth=true _auth=&amp;quot;ZGVwbG95ZXI6ZGVwbG95ZXI=&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;_auth是 username:password 的base64值，这样设置的好处是publish时就不用login了。&lt;/p&gt; &lt;p&gt;该文件是当前用户目录下的.npmrc文件&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;发布控件到npm私服中&lt;/h3&gt; &lt;p&gt;在package.json 配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;quot;publishConfig&amp;quot; : {     &amp;quot;registry&amp;quot; : &amp;quot;http://localhost:8081/repository/npm-hosted/&amp;quot;   } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在包根目录执行npm publish即可&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;注意：发布是npm-hosted，不是npm-group.&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;# 若不想在package.json配置，也可以在命令行指定 npm publish --registry=http://localhost:8081/repository/npm-hosted/ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Nexus3数据备份迁移&lt;/h2&gt; &lt;p&gt;内网环境下，很多包需要从外网移入，可以把相关包在外网安装测试成功后，然后将缓存的包直接复制到内网即可。&lt;/p&gt; &lt;h3&gt;Linux&lt;/h3&gt; &lt;h4&gt;仓库迁移&lt;/h4&gt; &lt;p&gt;Nexus的构件仓库都保存在sonatype-work目录中，该目录的位置由nexus/conf/nexus.properties配置文件指定。 仓库迁移需要两个过程：备份和还原&lt;/p&gt; &lt;h4&gt;备份仓库&lt;/h4&gt; &lt;p&gt;将sonatype-work文件夹整体备份即可，也可以选择只备份最重要的两个文件夹索引（indexer）和仓库（storage）&lt;/p&gt; &lt;h4&gt;还原仓库&lt;/h4&gt; &lt;p&gt;将备份好的sonatype-work文件拷贝到新的服务器中。然后修改nexus/conf/nexus.properties配置文件，重新指定仓库的目录。&lt;/p&gt; &lt;h3&gt;Windows&lt;/h3&gt; &lt;h4&gt;仓库迁移&lt;/h4&gt; &lt;p&gt;Nexus的构件仓库都保存在sonatype-work目录中，该目录的位置由bin/nexus.vmoptions配置文件指定（Dkaraf.data）。 仓库迁移需要两个过程：备份和还原&lt;/p&gt; &lt;h4&gt;备份仓库&lt;/h4&gt; &lt;p&gt;将sonatype-work文件夹整体备份即可，也可以选择只备份最重要的两个文件夹索引（indexer）和仓库（storage）&lt;/p&gt; &lt;h4&gt;还原仓库&lt;/h4&gt; &lt;p&gt;将备份好的sonatype-work文件拷贝到新的服务器中。然后修改bin/nexus.vmoptions配置文件，重新指定仓库的目录。&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 08 Oct 2023 02:28:24 GMT</pubDate>
    </item>
    <item>
      <title>单机版es中index状态为yellow问题处理</title>
      <link>https://maruifu.cn/article/290</link>
      <content:encoded>&lt;h1&gt;查询es中index状态&lt;/h1&gt; &lt;pre&gt;&lt;code class="language-cpp"&gt; curl  -X GET  -u  username:password    http://xx.xx.xx.xx:9200/_cluster/health?pretty {   &amp;quot;cluster_name&amp;quot; : &amp;quot;es7&amp;quot;,   &amp;quot;status&amp;quot; : &amp;quot;yellow&amp;quot; } &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-kotlin"&gt;$ curl  -u username:password    http://xx.xx.xx.xx:9200/_cat/indices   yellow open index01    1NCeOgzbQIqIX_OFUv-WTg 3 1   5   1  31.7kb  31.7kb green  open index02    -ug5H9CWS66n2_Q3_tGoiQ 3 0   0   0    624b    624b yellow open index03    WHBtQw_tRuOKN05oyPwf9w 1 1  24   7 174.8kb 174.8kb yellow open index04    tIWbmSlOToOBSoXDp45PgQ 5 1 141 466 421.1kb 421.1kb &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;ES index状态为yellow状态原因&lt;/h1&gt; &lt;p&gt;索引初始化脚本中，设置的 索引的分片副本数为  1 ，而单机版es，索引的分片副本无处安放，导致索引/集群状态为 yellow&lt;/p&gt; &lt;h1&gt;处理办法&lt;/h1&gt; &lt;p&gt;将相关索引的分片副本数改为0&lt;/p&gt; &lt;p&gt;注意：此管理操作需要使用es集群的 管理员账号&lt;/p&gt; &lt;pre&gt;&lt;code class="language-rust"&gt;curl -XPUT  'http://xx.xx.xx.xx:9200/${index_name}/_settings' \  -H 'Content-Type: application/json' \  -u  username:password   \ -d '{     &amp;quot;index&amp;quot; : {                &amp;quot;number_of_replicas&amp;quot;: 0     } }' &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;验证&lt;/h1&gt; &lt;pre&gt;&lt;code class="language-cpp"&gt;curl  -X GET -s   -u  username:password   http://xx.xx.xx.xx:9200/_cluster/health?pretty  {   &amp;quot;cluster_name&amp;quot; : &amp;quot;es7&amp;quot;,   &amp;quot;status&amp;quot; : &amp;quot;green&amp;quot;,   &amp;quot;timed_out&amp;quot; : false,   &amp;quot;number_of_nodes&amp;quot; : 1,   &amp;quot;number_of_data_nodes&amp;quot; : 1,   &amp;quot;active_primary_shards&amp;quot; : 13,   &amp;quot;active_shards&amp;quot; : 13,   &amp;quot;relocating_shards&amp;quot; : 0,   &amp;quot;initializing_shards&amp;quot; : 0,   &amp;quot;unassigned_shards&amp;quot; : 0,   &amp;quot;delayed_unassigned_shards&amp;quot; : 0,   &amp;quot;number_of_pending_tasks&amp;quot; : 0,   &amp;quot;number_of_in_flight_fetch&amp;quot; : 0,   &amp;quot;task_max_waiting_in_queue_millis&amp;quot; : 0,   &amp;quot;active_shards_percent_as_number&amp;quot; : 100.0 } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 07 Oct 2023 07:49:00 GMT</pubDate>
    </item>
    <item>
      <title>群里提问的智慧</title>
      <link>https://maruifu.cn/article/289</link>
      <content:encoded>&lt;h2&gt;提问之前&lt;/h2&gt; &lt;h3&gt;尝试自己解决&lt;/h3&gt; &lt;p&gt;善于搜索 ：Baidu，Google，Bing，AI ，ChatGpt&lt;/p&gt; &lt;p&gt;查看群文件、文件说明书&lt;/p&gt; &lt;p&gt;询问朋友&lt;/p&gt; &lt;p&gt;自己不断摸索，测试，折腾&lt;/p&gt; &lt;h3&gt;不能自己解決&lt;/h3&gt; &lt;p&gt;想明白自己要问什么&lt;/p&gt; &lt;p&gt;梳理准备你的问题&lt;/p&gt; &lt;h2&gt;怎样提问&lt;/h2&gt; &lt;h3&gt;用词准确，问题明确&lt;/h3&gt; &lt;h3&gt;描述清楚，信息充足&lt;/h3&gt; &lt;p&gt;准确有效的信息&lt;/p&gt; &lt;p&gt;问题表现/内容&lt;/p&gt; &lt;p&gt;做过什么尝试&lt;/p&gt; &lt;p&gt;想要问到什么&lt;/p&gt; &lt;p&gt;遇到问题之前你做了什么&lt;/p&gt; &lt;h3&gt;别问毫无意义的问题&lt;/h3&gt; &lt;p&gt;没有人理会&lt;/p&gt; &lt;p&gt;没有人在&lt;/p&gt; &lt;h2&gt;注意事项&lt;/h2&gt; &lt;h3&gt;提问之前做好冷场的准备&lt;/h3&gt; &lt;p&gt;也许这个问题网上随便搜搜就知道了&lt;/p&gt; &lt;p&gt;也许别人在忙&lt;/p&gt; &lt;p&gt;也许没人遇到过这种问题&lt;/p&gt; &lt;p&gt;也许...&lt;/p&gt; &lt;h3&gt;谦虚，别人没有义务为你解答问题&lt;/h3&gt; &lt;h3&gt;没有一定的自学能力，遇到问题只会伸手的不适合玩这个&lt;/h3&gt;</content:encoded>
      <pubDate>Sat, 07 Oct 2023 03:09:19 GMT</pubDate>
    </item>
    <item>
      <title>MacBook企业监管机屏蔽弹窗指南Sonoma</title>
      <link>https://maruifu.cn/article/288</link>
      <content:encoded>&lt;h2&gt;1关闭电脑SIP&lt;/h2&gt; &lt;p&gt;关机，然后长按电源键直至开机进入恢复模式，找到终端，&lt;/p&gt; &lt;p&gt;键入csrutil disable 回车执行。&lt;/p&gt; &lt;p&gt;等待执行结果，直到出现 System Integrity Protection is off.证明 SIP 已关闭&lt;/p&gt; &lt;h2&gt;2屏蔽指令&lt;/h2&gt; &lt;p&gt;重新开机，打开终端，依次键入&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo rm /var/db/ConfigurationProfiles/Settings/.cloudConfigHasActivationRecoro sudo rm /var/db/ConfigurationProfiles/Settings/.cloudConfigRecordFound sudo touch /var/db/ConfigurationProfiles/Settings/.cloudConfigProfilelnstalled sudo touch /var/db/ConfigurationProfiles/Settings/.cloudConfigRecordNotFound &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;3.成功&lt;/h2&gt;</content:encoded>
      <pubDate>Sat, 07 Oct 2023 01:27:00 GMT</pubDate>
    </item>
    <item>
      <title>CentOS 配置ssh和sftp服务分离</title>
      <link>https://maruifu.cn/article/287</link>
      <content:encoded>&lt;h2&gt;实现原理&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;创建两个“sshd”进程，一个作为ssh服务的deamon，一个作为sftp服务的deamon。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;ssh服务和sftp服务分离之前：&lt;/p&gt; &lt;p&gt;系统内开启ssh服务和sftp服务都是通过/usr/sbin/sshd这个后台程序监听22端口，而sftp服务作为一个子服务，是通过/etc/ssh/sshd_config配置文件中的Subsystem实现的，如果没有配置Subsystem参数，则系统是不能通过sftp访问的。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;操作步骤&lt;/h2&gt; &lt;h3&gt;创建两个deamon&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;ln -sf /usr/sbin/sshd /usr/sbin/sftpd &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;要实现ssh和sftp分离，分别监听不同的端口，可以通过创建两个‘/usr/sbin/sshd’后台程序，一个监听端口(ssh)，一个监听10022端口(sftp)，为了区分ssh和sftp服务的后台程序，这里将ssh服务的后台程序保持为/usr/sbin/sshd，而将sftp服务的后台程序改为/usr/sbin/sftpd。/usr/sbin/sftpd是/usr/sbin/sshd的一个链接，其内容完全相同。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;创建两个service&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;cp /usr/lib/systemd/system/sshd.service /etc/systemd/system/sftpd.service &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;CentOS使用systemd管理系统服务，ssh服务对应/usr/lib/systemd/system/sshd.service文件，实现sftp服务时可以将/usr/lib/systemd/system/sshd.service复制到/etc/systemd/system/sftpd.service，然后修改sftpd.service文件内容（使用修改好的sftpd.service文件即可）。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;其他需要分离的文件&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;拷贝/etc/pam.d/目录下的sshd文件，放到同目录，命名为：sftpd。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp /etc/pam.d/sshd /etc/pam.d/sftpd &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;拷贝/etc/ssh/目录下的sshd_config文件，放到同目录，命名为：sftpd_config。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp /etc/ssh/sshd_config /etc/ssh/sftpd_config &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;对service和rcsftpd进行软连接。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ln -sf /usr/sbin/service /usr/sbin/rcsftpd &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;拷贝/etc/sysconfig/目录下的sshd文件，放到同目录，命名为：sftp。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp /etc/sysconfig/sshd /etc/sysconfig/sftp &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;拷贝/var/run/目录下的sshd.pid文件，放到同目录，命名为：sftpd.pid。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp /var/run/sshd.pid /var/run/sftpd.pid &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时已经实现了ssh服务和sftp服务分离。但是，ssh服务和sftp服务并没有真正的分离，此时已然可以通过22号端口使用ssh服务和sftp服务。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;编辑/usr/sbin/sshd的配置文件/etc/ssh/sshd_config，将Subsystem参数注释掉，然后重启sshd服务，此时通过22端口无法访问sftp服务。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vi /etc/ssh/sshd_config &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;按“i”进入编辑模式，在“Subsystem”命令前加“#”，使本行变为注释。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# Subsystem sftp /usr/libexec/openssh/sftp-server &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;重启sshd服务。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl restart sshd.service &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看sshd服务状态。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl status sshd.service &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;显示如下信息示例。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;● sshd.service - OpenSSH server daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2021-08-24 10:35:23 CST; 18min ago Docs: man:sshd(8) man:sshd_config(5) Main PID: 91176 (sshd) CGroup: /system.slice/sshd.service └─91176 /usr/sbin/sshd -D  Aug 24 10:35:23 dggbenv01220 systemd[1]: Starting OpenSSH server daemon... Aug 24 10:35:23 dggbenv01220 sshd[91176]: Server listening on 0.0.0.0 port 22. Aug 24 10:35:23 dggbenv01220 sshd[91176]: Server listening on :: port 22. Aug 24 10:35:23 dggbenv01220 systemd[1]: Started OpenSSH server daemon. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改配置sftpd服务&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;修改/etc/systemd/system/目录下sftpd.service文件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;vim /etc/systemd/system/sftpd.service&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;按“i”进入编辑模式，修改以下内容：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Description=sftpd server daemon Type=notify EnvironmentFile=/etc/sysconfig/sftp ExecStart=/usr/sbin/sftpd -f /etc/ssh/sftpd_config &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;编辑完成后按“Esc”退出编辑模式，输入“:wq!”保存并退出。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;修改/etc/ssh/目录下的sftpd_config文件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;vim /etc/ssh/sftpd_config&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;按“i”进入编辑模式，找到第17行，将“Port 22”改为“Port &lt;em&gt;10022&lt;/em&gt;”。&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;找到第38行，将“#Perm itRootLogin yes”改为“PermitRootLogin no”。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;找到第116行，将“#PidFile /var/run/sshd.pid”改为“PidFile /var/run/sftpd.pid”。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;找到第132行，在“Subsystem sftp /usr/libexec/openssh/sftp-server”前加“#”使本行变为注释。另起一行添加“Subsystem sftp internal-sftp”。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;“&lt;em&gt;10022&lt;/em&gt;”表示端口号，可以是任意数值。&lt;/li&gt; &lt;li&gt;“#”表示该行的注释，去掉“#”就是取消该行的注释，并设置参数，将“yes”改成“no”，目的是禁止root用户登录。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;ol start="4"&gt; &lt;li&gt; &lt;p&gt;删除/var/run/目录下的sftpd.pid文件内容。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;rm -rf /var/run/sftpd.pid &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;启动sftpd服务。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl daemon-reload systemctl start sftpd &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;至此，sftpd服务已经配置完成并启动。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;通过命令查看其状态信息。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl status sftpd &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;● sftpd.service - OpenSSH server daemon    Loaded: loaded (/etc/systemd/system/sftpd.service; disabled; vendor preset: disabled)    Active: active (running) since Tue 2021-08-24 16:27:55 CST; 2s ago      Docs: man:sshd(8)            man:sshd_config(5)  Main PID: 111851 (sftpd)    CGroup: /system.slice/sftpd.service            └─111851 /usr/sbin/sftpd -f /etc/ssh/sftpd_config   Aug 24 16:27:55 dggbenv01220 systemd[1]: Starting OpenSSH server daemon... Aug 24 16:27:55 dggbenv01220 sftpd[111851]: Server listening on 0.0.0.0 port 10022. Aug 24 16:27:55 dggbenv01220 sftpd[111851]: Server listening on :: port 10022. Aug 24 16:27:55 dggbenv01220 systemd[1]: Started OpenSSH server daemon. &lt;/code&gt;&lt;/pre&gt; &lt;ol start="7"&gt; &lt;li&gt;可以看到sftpd服务已经在&lt;em&gt;10022&lt;/em&gt;端口上启动监听服务。&lt;/li&gt; &lt;li&gt;用FileZilla客户端进行验证。&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;验证操作&lt;/h2&gt; &lt;h3&gt;使用sftp协议访问22端口&lt;/h3&gt; &lt;p&gt;错误: FATAL ERROR: Received unexpected end-of-file from SFTP server&lt;/p&gt; &lt;p&gt;错误: 无法连接到服务器&lt;/p&gt; &lt;h3&gt;使用root帐号通过sftp协议访问10022端口&lt;/h3&gt; &lt;p&gt;错误: 认证失败。&lt;/p&gt; &lt;p&gt;错误: 严重错误: 无法连接到服务器&lt;/p&gt; &lt;h3&gt;使用普通帐号通过sftp协议访问10022端口&lt;/h3&gt; &lt;p&gt;可以连接成功，并上传下载文件。&lt;/p&gt; &lt;p&gt;二、CRC-8原理 模2除法   模2除法与算术除法类似，但每一位除的结果不影响其它位，即不向上一位借位，所以实际上就是异或。在循环冗余校验码（CRC）的计算中有应用到模2除法。&lt;/p&gt; &lt;p&gt;原文链接：https://support.huawei.com/enterprise/zh/doc/EDOC1100197618?section=j01y&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 01 Sep 2023 06:50:00 GMT</pubDate>
    </item>
    <item>
      <title>Mysql 5.7开启binlog日志</title>
      <link>https://maruifu.cn/article/286</link>
      <content:encoded>&lt;h3&gt;前言&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;binlog是MySQL的二进制日志，并且是MySQL中最重要的日志。binlog记录了对MySQL数据库执行更改的所有操作，包括对数据库表结构的变更，对数据的变更，例如CREATE、ALTER TABLE、INSERT、UPDATE、DELETE…，但是不会记录查询语句select。需要注意的是，如果是update操作，即使是没有数据更新，也会记录在binlog日志中，binlog日志是以事件形式记录，并且还包含语句所执行的消耗时间。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;查看binlog是否开启&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;show variables like 'log_bin'; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/09/01/image-20230901143108798.png" alt="image-20230901143108798" title="image-20230901143108798" /&gt;&lt;/p&gt; &lt;h3&gt;开启binlog&lt;/h3&gt; &lt;h4&gt;修改mysql配置文件&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;[mysqld] log-bin=mysql-bin server_id=1 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意: serverd一定要设置，否则会导致mysql无法启动&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;重新启动mysql&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 停止mysql服务 service mysql stop  # 启动mysql服务 service mysql start  # 重启mysql服务 service mysql restart    &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;扩展&lt;/h3&gt; &lt;h4&gt;应用&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;主从复制&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;master端开启binlog，master把二进制日志传递给slaves来达到master-slave数据保持一致。&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据恢复&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;可以通过mysqlbinlog工具解析binlog来恢复数据。&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;binlog日志常用命令&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;查看所有的binlog日志列表&lt;/p&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;show master logs; &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看master状态，即最后一个binlog日志的编号名称，及其最后一个操作时间pos结束点值&lt;/p&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;show master status; &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;flush刷新binlog日志，此刻之后会产生一个新编号的binlog日志文件&lt;/p&gt; &lt;pre&gt;&lt;code class="language-arduino"&gt;flush logs; &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;bin-log日志存放地址&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;binlog存放在/var/lib/mysql里面的，如果是docker，则在相应的映射目录&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;配置存放位置、过期时间&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;在MySQL配置文件my.cnf或者my.ini中[mysqld]标签内修改&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;# 配置定时清理 expire_logs_days = 5 # 配置修改后的日志路径 log-bin=/home/logs/mysql-bin &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;配置每个日志文件的大小&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;在MySQL配置文件my.cnf或者my.ini中[mysqld]标签内修改&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;# binlog每个日志文件大小 max_binlog_size = 100m &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;binlog格式&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-ini"&gt;# binlog日志格式，MySQL默认采用的是STATEMENT，建议使用MIXED binlog_format = MIXED &lt;/code&gt;&lt;/pre&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;复制模式&lt;/th&gt;&lt;th&gt;优点&lt;/th&gt;&lt;th&gt;缺点&lt;/th&gt;&lt;th&gt;原理&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;STATEMENT模式&lt;/td&gt;&lt;td&gt;- 减少binlog日志量，节约IO，提高性能&lt;br&gt;- 适用于简单的SQL语句&lt;/td&gt;&lt;td&gt;- 某些情况会导致master-slave中的数据不一致，例如sleep()，last_insert_id()等&lt;/td&gt;&lt;td&gt;基于SQL语句的复制（statement-based replication），每一条会修改数据的sql语句都会记录到binlog中&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;ROW模式&lt;/td&gt;&lt;td&gt;- 任何情况都可以复制，并且不会出现特定情况下存储过程、function等调用或者触发无法被正确复制的问题&lt;/td&gt;&lt;td&gt;- binlog日志文件会非常大&lt;br&gt;- master上执行update语句时，所有变化都会写到binlog里面，SBR只会写一次，所以会导致频繁发生binlog的并发写问题&lt;/td&gt;&lt;td&gt;基于行的复制（row-based replication），不记录每条sql语句的上下文信息，仅记录哪条数据被修改了，修改成什么样。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;MIXED模式&lt;/td&gt;&lt;td&gt;一般的复制使用STATEMENT模式保存binlog，对于STATEMENT模式无法复制的操作使用ROW模式保存binlog，MySQL会根据执行的SQL语句选择日志保存方式&lt;/td&gt;&lt;td&gt;- 实现较为复杂&lt;/td&gt;&lt;td&gt;两种模式的混合使用&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Fri, 01 Sep 2023 06:40:00 GMT</pubDate>
    </item>
    <item>
      <title>docker 部署的gogs创建管理员账户</title>
      <link>https://maruifu.cn/article/285</link>
      <content:encoded>&lt;h2&gt;查找gogs应用容器&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# docker  ps  8f0cd2b10c84   gogs/gogs:latest    &amp;quot;/app/gogs/docker/st…&amp;quot;    3000/tcp, 0.0.0.0:45-&amp;gt;22/tcp, 0.0.0.0:370-&amp;gt;3001/tcp                          gogs-gogs &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;进入gogs容器&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;docker exec -it 8f0cd2b10c84  bash  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;切换git用户&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;su - git &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;进入执行目录&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;/app/gogs &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建管理员用户&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; ./gogs admin create-user --name=root --password=123456 --email=email@maruifu.cn --admin=true &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 25 Jul 2023 05:50:00 GMT</pubDate>
    </item>
    <item>
      <title>excel 无法编辑单元格，忘记保护密码，怎么撤销保护密码</title>
      <link>https://maruifu.cn/article/284</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;今天收到一份excel文件，只能编辑有限的几个单元格，其他单元格都是使用公式联动的，就想着看下公式是什么，但是那些单元格都是无法用鼠标点击进行编辑，最后发现是因为设置了excel文件的发行人使用了密码保护功能。 折腾了一番终于把密码去掉了，记录一下。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;一、保护工作表&lt;/h2&gt; &lt;p&gt;步骤：点击【审阅】-【保护工作表】-勾选【选定锁定单元格】和【选定未锁定的单元格】-【确定】。随后设置单元格格式、插入行、删除行等都无法进行操作。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/07/18/85837a38ecd24a0facf88564c5a45d05.gif" alt="在这里插入图片描述" title="在这里插入图片描述" /&gt;&lt;/p&gt; &lt;h2&gt;二、解除工作表密码保护&lt;/h2&gt; &lt;h3&gt;1、查看代码&lt;/h3&gt; &lt;p&gt;步骤：右键【sheet1】-【查看代码】-打开代码窗口。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/07/18/9c3b62fe477149b7a69874f117290edd.gif" alt="在这里插入图片描述" title="在这里插入图片描述" /&gt;&lt;/p&gt; &lt;h3&gt;2、插入代码&lt;/h3&gt; &lt;p&gt;代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Sub DeletePW()  ActiveSheet.Protect DrawingObjects:=True, Contents:=True, AllowFiltering:=True  ActiveSheet.Protect DrawingObjects:=False, Contents:=True, AllowFiltering:=True  ActiveSheet.Protect DrawingObjects:=True, Contents:=True, AllowFiltering:=True  ActiveSheet.Protect DrawingObjects:=False, Contents:=True, AllowFiltering:=True  ActiveSheet.Unprotect  End Sub &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;步骤：粘贴代码，点击【运行】即可。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/07/18/c8dc552eddd1495a93946864b36a0790.gif" alt="在这里插入图片描述" title="在这里插入图片描述" /&gt;&lt;/p&gt; &lt;p&gt;至此，问题得到解决。&lt;/p&gt; &lt;p&gt;转载于&lt;a href="https://blog.csdn.net/shadow_2011/article/details/121632706" target="_blank"&gt;EchoCoder&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 18 Jul 2023 02:01:00 GMT</pubDate>
    </item>
    <item>
      <title>jenkins部署前后端分离项目</title>
      <link>https://maruifu.cn/article/283</link>
      <content:encoded>&lt;h2&gt;Git安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;yum install git &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Jdk安装&lt;/h2&gt; &lt;p&gt;官网下载 JDK8：http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html&lt;/p&gt; &lt;pre&gt;&lt;code&gt;安装 # 创建目录 mkdir /usr/local/java # 拷贝安装包到指定目录 cp jdk-8u301-linux-x64.tar.gz   /usr/local/java/ # 进入java安装目录 cd /usr/local/java/ # 解压jdk安装包 tar -zxvf jdk-8u301-linux-x64.tar.gz # 删除安装包（可选） rm -rf jdk-8u301-linux-x64.tar.gz  配置环境变量 # 编辑配置文件 vim /etc/profile # 最下面添加如下内容： export JAVA_HOME=/usr/local/java/jdk1.8.0_301 export CLASSPATH=$JAVA_HOME/lib/ export PATH=$PATH:$JAVA_HOME/bin   # 然后ESC :wq保存退出 :wq # 配置生效 source /etc/profile # 检查安装情况 java -version &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Node安装&lt;/h2&gt; &lt;p&gt;node下载地址： http://nodejs.cn/download/current/ https://nodejs.org/dist/&lt;/p&gt; &lt;p&gt;下载离线包，上传到&lt;code&gt;/usr/local/node&lt;/code&gt;&lt;/p&gt; &lt;p&gt;创建软链：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 解压 tar -zxvf node-v16.17.1-linux-x64.tar.gz # 重命名 mv node-v16.17.1-linux-x64 node-v16.17.1 ln -s /usr/local/node/node-v16.17.1/bin/node /usr/local/bin ln -s /usr/local/node/node-v16.17.1/bin/npm /usr/local/bin &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Maven 安装&lt;/h2&gt; &lt;h3&gt;下载&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;wget  https://dlcdn.apache.org/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;解压&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;tar -xzvf apache-maven-3.6.1-bin.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置系统maven环境&lt;/h3&gt; &lt;p&gt;编辑系统环境文件&lt;code&gt;profile&lt;/code&gt;，该文件类似于windows里的环境变量。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vi /etc/profile &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注意，配置的变量，请指向自己解压的maven路径：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;export MAVEN_HOME=/usr/local/maven/apache-maven-3.6.1 export PATH=$MAVEN_HOME/bin:$PATH &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;:wq&lt;/code&gt;，退出保存，然后重新加载一下配置：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;source /etc/profile &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看maven版本，测试配置生效：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-armasm"&gt;mvn -v &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置镜像加速+指定仓库地址&lt;/h3&gt; &lt;p&gt;配置jar包下载路径，路径指向自己的&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;&amp;lt;localRepository&amp;gt;/usr/local/maven/repository&amp;lt;/localRepository&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;配置阿里镜像加速，默认是从中央仓库拉取。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;mirrors&amp;gt;  &amp;lt;mirror&amp;gt;    &amp;lt;id&amp;gt;alimaven&amp;lt;/id&amp;gt;    &amp;lt;name&amp;gt;aliyun maven&amp;lt;/name&amp;gt;    &amp;lt;url&amp;gt;http://maven.aliyun.com/nexus/content/groups/public/&amp;lt;/url&amp;gt;    &amp;lt;mirrorOf&amp;gt;central&amp;lt;/mirrorOf&amp;gt;          &amp;lt;/mirror&amp;gt; &amp;lt;/mirrors&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;:wq&lt;/code&gt;，退出保存。&lt;/p&gt; &lt;h2&gt;jenkins安装&lt;/h2&gt; &lt;p&gt;下载地址 https://mirrors.jenkins.io/war-stable/&lt;/p&gt; &lt;h3&gt;下载&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;wget https://sg.mirror.servanamanaged.com/jenkins/war/2.346/jenkins.war &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;nohup java  -jar jenkins.war --httpPort=6888  2&amp;gt;&amp;amp;1 &amp;amp; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;插件&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# maven项目插件 Maven Integration   # 备份插件 ThinBackup    # 可以在构建时进行分支选择 Git Parameter Plug-In # NODE前端发布 NODEJS # 角色配置 Role-based Authorization Strategy # 开机自启 Startup # 日格式化 Date Paramete &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;Maven 配置&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;/usr/local/maven/apache-maven-3.6.1/conf/settings.xml &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;JDK&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#jdk8     /usr/local/java/jdk1.8.0_371 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;Maven&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#Name   maven3.6 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# MAVEN_HOME    /usr/local/maven/apache-maven-3.6.1 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;NodeJs&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/06/09/image-20230609094732369.png" alt="image-20230609094732369" title="image-20230609094732369" /&gt;&lt;/p&gt; &lt;h2&gt;后端部署配置&lt;/h2&gt; &lt;h3&gt;General&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;后端-XX系统-演示环境 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;源码管理&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;http://192.168.1.7:1234/mrfr/backend/mrfResource.git &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;构建环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;pom.xml clean package  -Dmaven.test.skip=true &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;Post Steps&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#!/bin/bash  #环境 ENV=test  #源jar路径，打包完成之后，target目录下的jar包名称 JAR_NAME=mrf-admin  #target打包生成jar包的目录 # 以具体的打包位置为准，可以先构建一次项目，通过日志查看打包的目录 JAR_PATH=/home/maruifu/.jenkins/workspace/后端-XX系统-测试环境/mrf-admin/target       #打包完成之后，把jar包移动到运行jar包的目录 JAR_WORK_PATH=/usr/local/backend/mrfr/test   echo &amp;quot;查询进程id--&amp;gt;${JAR_NAME}_${ENV}&amp;quot; PID=`ps -ef |grep java|grep &amp;quot;${JAR_NAME}_${ENV}&amp;quot; |grep -v grep|awk '{print $2}'`  if [ x&amp;quot;$PID&amp;quot; != x&amp;quot;&amp;quot; ]; then   kill -TERM $PID   sleep 1         echo &amp;quot;等待1秒钟&amp;quot;          sleep 1         echo &amp;quot;等待2秒钟&amp;quot;    echo &amp;quot;${JAR_NAME}_${ENV} 退出.&amp;quot;  else   echo &amp;quot;${JAR_NAME}_${ENV} 已经停止了&amp;quot;  fi echo &amp;quot;正在结束进程完成&amp;quot;  echo &amp;quot;正在复制jar包到执行目录&amp;quot; cp $JAR_PATH/$JAR_NAME.jar $JAR_WORK_PATH/${JAR_NAME}_${ENV}.jar cd $JAR_WORK_PATH  echo &amp;quot;正在修改文件权限&amp;quot; chmod 755 ${JAR_NAME}_${ENV}.jar   echo &amp;quot;正在启动${JAR_NAME}&amp;quot; BUILD_ID=dontKillMe nohup java -jar ${JAR_NAME}_${ENV}.jar   --spring.profiles.active=${ENV}  2&amp;gt;&amp;amp;1 &amp;amp; echo &amp;quot;${JAR_NAME}启动成功&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;前端部署配置&lt;/h2&gt; &lt;h3&gt;General&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;前端-XX系统-前台-测试环境 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;源码管理&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;http://192.168.1.7:1234/mrfr/frontend/backstage.git &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;构建环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Provide Node &amp;amp; npm bin/ folder to PATH &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;Post Steps&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#!/bin/bash  #环境 ENV=test   #打包生成dist的目录 DIST_PATH=/home/maruifu/.jenkins/workspace/前端-XX系统-前台-测试环境   #打包完成之后，把DIST移动到Nginx执行的目录 UI_WORK_PATH=/usr/local/frontend/mrfr-ui/   echo  &amp;quot;查询node版本！&amp;quot; node -v &amp;amp;&amp;amp; echo  &amp;quot;正在安装依赖！&amp;quot; npm install &amp;amp;&amp;amp; echo  &amp;quot;正在删除上次打包的文件&amp;quot; rm -rf  $(ls -d *dist* | tail -1) echo  &amp;quot;安装构建打包！&amp;quot; npm run build:${ENV}   DIST_NAME=$(ls -d *dist* | tail -1) echo  &amp;quot;获取打包文件夹名:${DIST_NAME}&amp;quot;  echo  &amp;quot;正在复制文件到执行目录&amp;quot;  cp -r  ${DIST_PATH}/${DIST_NAME} ${UI_WORK_PATH} echo  &amp;quot;正在删除${UI_WORK_PATH}dist&amp;quot;  rm -rf  ${UI_WORK_PATH}dist  cd ${UI_WORK_PATH} mv  ${DIST_NAME}    dist echo  &amp;quot;部署完成了！&amp;quot; echo  &amp;quot;部署完成了！&amp;quot; echo  &amp;quot;部署完成了！&amp;quot;  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;权限配置&lt;/h2&gt; &lt;h3&gt;角色设置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/06/14/image-20230614094729103.png" alt="image-20230614094729103" title="image-20230614094729103" /&gt;&lt;/p&gt; &lt;h3&gt;用户设置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/06/14/image-20230614094839898.png" alt="image-20230614094839898" title="image-20230614094839898" /&gt;&lt;/p&gt; &lt;h2&gt;多种备份方式&lt;/h2&gt; &lt;h3&gt;拷贝文件备份&lt;/h3&gt; &lt;p&gt;手动备份jekins_home目录的以下文件：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;config.xml hudson*.xml 插件名*.xml job目录 user目录 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;备份与还原-ThinBackup&lt;/h3&gt; &lt;h4&gt;备份&lt;/h4&gt; &lt;p&gt;1、以管理员身份登录Jenkins，点击 系统管理 – ThinBackup&lt;/p&gt; &lt;p&gt;2、点击Settings ，设置备份文件的保存目录，同时可以设置备份的一些特殊属性&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/30/image-20231030094246925.png" alt="image-20231030094246925" title="image-20231030094246925" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/30/image-20231030094408944.png" alt="image-20231030094408944" title="image-20231030094408944" /&gt;&lt;/p&gt; &lt;p&gt;3、点击 Backup Now，即可完成备份&lt;/p&gt; &lt;h4&gt;还原&lt;/h4&gt; &lt;p&gt;1、把ThinBackup备份的文件(一般是文件夹或zip)，拷贝到 settings中设置的备份目录下或者 设置 Backup directory 为备份文件目录&lt;/p&gt; &lt;p&gt;2、点击 ThinBackup – Restore ，就还原成功了。&lt;/p&gt; &lt;h2&gt;应用回滚&lt;/h2&gt; &lt;h3&gt;创建选择参数&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/30/image-20231030140912026.png" alt="image-20231030140912026" title="image-20231030140912026" /&gt;&lt;/p&gt; &lt;h3&gt;设置GIT地址&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/10/30/image-20231030141019965.png" alt="image-20231030141019965" title="image-20231030141019965" /&gt;&lt;/p&gt; &lt;h3&gt;添加脚本-应用备份&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;case $status in   deploy)     echo &amp;quot;创建当前构建版本备份目录&amp;quot;     bak_dir=&amp;quot;${WORKSPACE}/bak/${BUILD_NUMBER}&amp;quot;     if [ -d $bak_dir ]     then       echo &amp;quot;目录已存在&amp;quot;     else       mkdir -p $bak_dir     fi     echo &amp;quot;开始构建...&amp;quot;     mvn clean package -Dmaven.test.skip=true     echo &amp;quot;构建结束&amp;quot;     cp -f ${WORKSPACE}/maruifu-admin/target/*.jar $bak_dir     echo &amp;quot;当前版本已备份完成!&amp;quot;     ;;   rollback)     echo &amp;quot;创建当前构建版本备份目录&amp;quot;     bak_dir=&amp;quot;${WORKSPACE}/bak/${BUILD_NUMBER}&amp;quot;     if [ -d $bak_dir ]     then       echo &amp;quot;目录已存在&amp;quot;     else       mkdir -p $bak_dir     fi     echo &amp;quot;开始回滚...&amp;quot;     echo &amp;quot;回滚的版本号是: ${version}&amp;quot;     cp -f ${WORKSPACE}/bak/$version/*.jar $bak_dir     ;;   *)     exit     ;; esac &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;添加脚本-删除多余备份&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 保留最新的5个 KeepVersionNum=5 # 备份所有版本所在目录 FileDir=${WORKSPACE}/bak/ cd $FileDir # 备份所在目录中有多少目录 FileNum=`ls -l |grep &amp;quot;^d&amp;quot; | wc -l`  echo &amp;quot;开始检查构建旧版本个数，最多保留 $KeepVersionNum 个最新版本&amp;quot; echo &amp;quot;当前保留版本数量: $FileNum&amp;quot; # 当前备份版本的数量 &amp;gt; 想要保留的版本数量，则执行以下循环 while (( $FileNum &amp;gt; $KeepVersionNum )) do   # 将当前目录下的所有目录以时间正序排序，head -1显示第一个   OldFile=$(ls -rt | head -1)   echo &amp;quot;$(date &amp;quot;+%Y:%m:%d %H:%M:%S&amp;quot;) 删除多余旧版本: ${OldFile}&amp;quot;   # 删除多出来的旧版本   rm -rf $FileDir/$OldFile   let &amp;quot;FileNum--&amp;quot; done  echo &amp;quot;正在部署的版号是：${BUILD_NUMBER}&amp;quot;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;添加脚本-应用启动&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; # 备份所有版本所在目录 FileDir=${WORKSPACE}/bak cd $FileDir # 将当前目录下的所有目录以时间正序排序，head -1显示第一个 lastFile=$(ls -t | head -1) cp -f $FileDir/$lastFile/*.jar /app/backend/dms/test-web  cd /app/backend/dms/test-web  /app/backend/dms/test-web/dms.sh restart  echo &amp;quot;部署成功&amp;quot; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 14 Jun 2023 01:56:00 GMT</pubDate>
    </item>
    <item>
      <title>Alma Linux mysql安装问题</title>
      <link>https://maruifu.cn/article/282</link>
      <content:encoded>&lt;h2&gt;未找到匹配的参数: mysql-community-server&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# yum -y install mysql-community-server     未找到匹配的参数: mysql-community-server 错误：没有任何匹配: mysql-community-server  #解决办法 更新源,后再次安装  rpm -Uvh  https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;没有东西可提供  libtinfo.so.5()(64bit)&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; [root@localhost ~]# yum install -y mysql-community-server  # 报错：  - 没有东西可提供 libncurses.so.5()(64bit)（mysql-community-client-5.7.9-1.el7.x86_64 需要）   - 没有东西可提供 libtinfo.so.5()(64bit)（mysql-community-client-5.7.9-1.el7.x86_64 需要） (尝试添加 '--skip-broken' 来跳过无法安装的软件包 或 '--nobest' 来不只使用软件包的最佳候选)  # 解决办法：安装依赖库  wget https://dl.rockylinux.org/pub/rocky/9/devel/x86_64/os/Packages/n/ncurses-compat-libs-6.2-8.20210508.el9.x86_64.rpm  rpm -ivh ncurses-compat-libs-6.2-8.20210508.el9.x86_64.rpm   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;没有任何匹配: mysql-community-server&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# yum install -y mysql-community-server  # 报错：  所有的匹配结果均已经被参数的模块化过滤条件筛除: mysql-community-server 错误：没有任何匹配: mysql-community-server  #解决办法：关掉mysql模块，再次执行 yum module disable mysql &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;错误的公钥？ 错误：GPG 检查失败&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# yum install -y mysql-community-server  # 报错：  下载的软件包保存在缓存中，直到下次成功执行事务。 您可以通过执行 'yum clean packages' 删除软件包缓存。 错误：GPG 检查失败   #解决办法： 删除后安装包后 更新key，然后再次安装 [root@localhost ~]# yum clean packages [root@localhost ~]# rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 [root@localhost ~]# yum install -y mysql-community-server   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;mariadb 冲突&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# yum install -y mysql-community-server   # 报错：  错误：事物测试失败：   file /etc/my.cnf from install of mysql-community-server-5.7.42-1.el7.x86_64 conflicts with file from package mariadb-connector-c-config-3.2.6-1.el9_0.noarch  # 原因：  从错误信息中看，是冲突了，因为错误信息中有写 `conflicts with file from package`，但是这是一个全新的Linux，正常没有安装过MySQL也没有安装过[Mariadb]，所以猜测是该Linux机器内嵌集成了该Mariadb包，**造成冲突**。  # 解决办法 查看本机被内嵌的Mariadb，使用`rpm -e --nodeps package`将内嵌集成的Mariadb卸载掉   # 查看本机被内嵌的Mariadb [root@localhost ~]# rpm -qa | grep mariadb # 卸载Mariadb [root@localhost ~]# rpm -e --nodeps mariadb-connector-c-config-3.2.6-1.el9_0.noarch [root@localhost ~]# rpm -e --nodeps mariadb-connector-c-3.2.6-1.el9_0.x86_64 [root@localhost ~]# yum clean packages [root@localhost ~]# yum install -y mysql-community-server  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 09 Jun 2023 01:31:00 GMT</pubDate>
    </item>
    <item>
      <title>使用AI配合Typora智能画图</title>
      <link>https://maruifu.cn/article/281</link>
      <content:encoded>&lt;h2&gt;一、流程图&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;```mermaid&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;竖向（TD 表示从上到下）&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;graph TD;     A--&amp;gt;B;     A--&amp;gt;C;     B--&amp;gt;D;     D--&amp;gt;E;     D--&amp;gt;F; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112649999.png" alt="image-20230525112649999" title="image-20230525112649999" /&gt;&lt;/p&gt; &lt;h3&gt;横向（LR 表示从左到右）&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-html"&gt;graph LR;   A[方形]--&amp;gt;B(圆角)   B--&amp;gt;C{条件a}   C--&amp;gt;|a=1|D[结果1]   C--&amp;gt;|a=2|E[结果2] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112705937.png" alt="image-20230525112705937" title="image-20230525112705937" /&gt;&lt;/p&gt; &lt;h3&gt;标准（竖向）&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;```flow&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;st=&amp;gt;start: 开始框   op=&amp;gt;operation: 处理框   cond=&amp;gt;condition: 判断框(是或否?)   sub1=&amp;gt;subroutine: 子流程   io=&amp;gt;inputoutput: 输入输出框   e=&amp;gt;end: 结束框   st-&amp;gt;op-&amp;gt;cond   cond(yes)-&amp;gt;io-&amp;gt;e   cond(no)-&amp;gt;sub1(right)-&amp;gt;op &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112739141.png" alt="image-20230525112739141" title="image-20230525112739141" /&gt;&lt;/p&gt; &lt;h3&gt;标准（横向）&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;st=&amp;gt;start: 开始框   op=&amp;gt;operation: 处理框   cond=&amp;gt;condition: 判断框(是或否?)   sub1=&amp;gt;subroutine: 子流程   io=&amp;gt;inputoutput: 输入输出框   e=&amp;gt;end: 结束框   st(right)-&amp;gt;op(right)-&amp;gt;cond   cond(yes)-&amp;gt;io(bottom)-&amp;gt;e   cond(no)-&amp;gt;sub1(right)-&amp;gt;op &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112752710.png" alt="image-20230525112752710" title="image-20230525112752710" /&gt;&lt;/p&gt; &lt;h2&gt;二、UML时序图&lt;/h2&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;先输入```mermaid (或）sequence&lt;/li&gt; &lt;li&gt;-&amp;gt;&amp;gt; 代表实线箭头，–&amp;gt;&amp;gt; 则代表虚线箭头&lt;/li&gt; &lt;li&gt;-&amp;gt; 直线，–&amp;gt;虚线&lt;/li&gt; &lt;li&gt;使用sequenceDiagram 则不使用``sequence&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h3&gt;简单&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;客户-&amp;gt;&amp;gt;银行柜台: 我要存钱   银行柜台-&amp;gt;&amp;gt;后台: 改一下这个账户数字哦   后台-&amp;gt;&amp;gt;银行柜台: 账户的数字改完了，明天起息   银行柜台-&amp;gt;&amp;gt;客户: 好了，给你回单 ，下一位 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112802974.png" alt="image-20230525112802974" title="image-20230525112802974" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;对象A-&amp;gt;对象B:对象B你好吗（请求）   Note right of 对象B:对象B的描述（提示）   Note left of 对象A:提示   对象B--&amp;gt;&amp;gt;对象A:我很好（响应）   对象A-&amp;gt;&amp;gt;对象B:你确定？ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112813357.png" alt="image-20230525112813357" title="image-20230525112813357" /&gt;&lt;/p&gt; &lt;h3&gt;复杂&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;sequenceDiagram     title:标题：复杂使用     对象A-&amp;gt;&amp;gt;对象B:对象B你好吗（请求）     Note right of 对象B:对象B的描述（提示）     Note left of 对象A:提示     对象B--&amp;gt;&amp;gt;对象A:我很好（响应）     对象B-&amp;gt;&amp;gt;对象C:你好吗？     对象C--&amp;gt;&amp;gt;对象A: B找我了     对象A-&amp;gt;&amp;gt;对象B:你确定？     note over 对象C,对象B:朋友     participant D     note right of D:没人陪我 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112832306.png" alt="image-20230525112832306" title="image-20230525112832306" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sequenceDiagram   participant A   participant B   participant C   A-&amp;gt;&amp;gt;C:hello   loop health    C-&amp;gt;&amp;gt;C:no    end   Note right of C:you should eat&amp;lt;br/&amp;gt; doctor   B--&amp;gt;&amp;gt;A:nice   C-&amp;gt;&amp;gt;B:how are you?   B--&amp;gt;&amp;gt;C:great  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525112908991.png" alt="image-20230525112908991" title="image-20230525112908991" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sequenceDiagram   participant A   participant B   participant C   participant D   title:&amp;quot;练习时序图&amp;quot;   A-&amp;gt;&amp;gt;B:request   B-&amp;gt;&amp;gt;B:verify sign   B-&amp;gt;&amp;gt;C:123   C--&amp;gt;&amp;gt;B:321   B-&amp;gt;&amp;gt;C:456   C-&amp;gt;&amp;gt;C:code   C-&amp;gt;&amp;gt;D:789   D--&amp;gt;&amp;gt;B:987   alt yes   Note right of B:yes的结果   else no   B--&amp;gt;&amp;gt;D:login   D--&amp;gt;&amp;gt;B:login success   end   B-&amp;gt;&amp;gt;B:加密   B--&amp;gt;&amp;gt;A:return   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-202305251129388004276cb79173a9018.png" alt="image-20230525112938800" title="image-20230525112938800" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sequenceDiagram   title:时序图例子   Alice-&amp;gt;&amp;gt;Alice:自言自语   Alice--&amp;gt;&amp;gt;John:hello john,   %% over 可以用于单独一个角色上，也可以用于相邻的两个角色间：   note over Alice,John:friend      %% loop 后跟循环体说明文字   loop healthcheck    John--&amp;gt;&amp;gt;John:Fight agaist hypochondra   end      note right of John: Rational      John--&amp;gt;&amp;gt;Alice:Great!   John-&amp;gt;&amp;gt;Bob:How about you?      %% 控制焦点：用来表示时序图中对象执行某个操作的一段时间   %% activate 角色名：表示激活控制焦点   activate Bob   Bob--&amp;gt;&amp;gt;John:Jolly good!   %% deactivate 角色名 表示控制焦点结束     deactivate Bob          Alice-&amp;gt;&amp;gt;+Bob: Hello Bob, how are you?          rect rgb(175, 255, 212)     alt is sick     Bob--&amp;gt;&amp;gt;Alice: Not so good :(     else is well     Bob--&amp;gt;&amp;gt;Alice: Feeling fresh like a daisy     end     opt Extra response     Bob--&amp;gt;&amp;gt;Alice: Thanks for asking     end     end          loop communicating         Alice-&amp;gt;&amp;gt;+John: asking some questions         John--&amp;gt;&amp;gt;-Alice: answer      end          par Alice to John       Alice-&amp;gt;&amp;gt;John: Bye     and Alice to Bob       Alice-&amp;gt;&amp;gt;Bob: Bye     end   Alice-xJohn: 这是一个异步调用     Alice--xBob: 这是一个异步调用  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113421181.png" alt="image-20230525113421181" title="image-20230525113421181" /&gt;&lt;/p&gt; &lt;h3&gt;标准&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;%% 时序图例子,-&amp;gt; 直线，--&amp;gt;虚线，-&amp;gt;&amp;gt;实线箭头   sequenceDiagram     participant 张三     participant 李四     张三-&amp;gt;王五: 王五你好吗？     loop 健康检查         王五-&amp;gt;王五: 与疾病战斗     end     Note right of 王五: 合理 食物 &amp;lt;br/&amp;gt;看医生...     李四--&amp;gt;&amp;gt;张三: 很好!     王五-&amp;gt;李四: 你怎么样?     李四--&amp;gt;王五: 很好!A  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113203252.png" alt="image-20230525113203252" title="image-20230525113203252" /&gt;&lt;/p&gt; &lt;h2&gt;三、甘特图&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;%% 语法示例         gantt         dateFormat  YYYY-MM-DD         title 软件开发甘特图         section 设计         需求                      :done,    des1, 2014-01-06,2014-01-08         原型                      :active,  des2, 2014-01-09, 3d         UI设计                     :         des3, after des2, 5d     未来任务                     :         des4, after des3, 5d         section 开发         学习准备理解需求                      :crit, done, 2014-01-06,24h         设计框架                             :crit, done, after des2, 2d         开发                                 :crit, active, 3d         未来任务                              :crit, 5d         耍                                   :2d           section 测试         功能测试                              :active, a1, after des3, 3d         压力测试                               :after a1  , 20h         测试报告                               : 48h  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113216460.png" alt="image-20230525113216460" title="image-20230525113216460" /&gt;&lt;/p&gt; &lt;h2&gt;四、类图&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;语法解释：&amp;lt;|-- 表示继承，+ 表示 public，- 表示 private，学过 Java 的应该都知道。 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;classDiagram       Animal &amp;lt;|-- Duck       Animal &amp;lt;|-- Fish       Animal &amp;lt;|-- Zebra       Animal : +int age       Animal : +String gender       Animal: +isMammal()       Animal: +mate()       class Duck{           +String beakColor           +swim()           +quack()       }       class Fish{           -int sizeInFeet           -canEat()       }       class Zebra{           +bool is_wild           +run()       }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113227098.png" alt="image-20230525113227098" title="image-20230525113227098" /&gt;&lt;/p&gt; &lt;h2&gt;五、&lt;strong&gt;状态图&lt;/strong&gt;&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-html"&gt;stateDiagram     [*] --&amp;gt; s1     s1 --&amp;gt; [*] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113235395.png" alt="image-20230525113235395" title="image-20230525113235395" /&gt;&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;六、饼图&lt;/strong&gt;&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;pie     title Key elements in Product X     &amp;quot;Calcium&amp;quot; : 42.96     &amp;quot;Potassium&amp;quot; : 50.05     &amp;quot;Magnesium&amp;quot; : 10.01     &amp;quot;Iron&amp;quot; :  5 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/05/25/image-20230525113246148.png" alt="image-20230525113246148" title="image-20230525113246148" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 25 May 2023 03:25:00 GMT</pubDate>
    </item>
    <item>
      <title>如何使内网ip能够实现HTTPS访问</title>
      <link>https://maruifu.cn/article/280</link>
      <content:encoded>&lt;p&gt;　在公司内部网络研发过程中，是使用的内部服务器进行发布，因此访问的ip也是内部的ip。这时候如果想对应用采用https访问的话，就需要配置对应的证书，但是市面上的证书需要使用到域名，并且基本都是基于互联网进行的认证，这种场景，在实际内网环境下，就变得难受，既不方便切换https后的调试，也不方便验证是否https配置都能成功，基于上述的情况，想到在内部服务器上生成证书，并且证书的认证地址也指向到ip，经过查找相关资料，遇到同样问题的还不少，现给出经过实际验证可行的方法。&lt;/p&gt; &lt;h2&gt;介绍&lt;/h2&gt; &lt;p&gt;mkcert是一个使用go语言编写的生成本地自签证书的小程序，具有跨平台，使用简单，支持多域名，自动信任CA等一系列方便的特性可供本地开发时快速创建https环境使用。&lt;/p&gt; &lt;h2&gt;下载&lt;/h2&gt; &lt;p&gt;其最新版本地址如下：&lt;a href="https://github.com/FiloSottile/mkcert/releases/latest" target="_blank"&gt;https://github.com/FiloSottile/mkcert/releases/latest&lt;/a&gt;&lt;/p&gt; &lt;p&gt;根据内部服务器的操作系统类型下载对应的windows/Linux/arm版本即可,我这里是x86的。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;　# 赋予权限 　chmod +x mkcert-v1.4.4-linux-amd64 　# 将mkcert文件移动到bin目录下 　sudo cp mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert 　# 安装证书 　mkcert -install 　 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;生成证书&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; # 得到证书的根目录，将该目录下的rootCA.pem文件下载，将生成的证书下载到需要访问该ip的pc机上备用 　mkcert -CAROOT 　# 将可信ip写入，并生成对应的ssl证书,执行中可能会要求输入密码，需要记住该密码，后续配置https时会用到 　mkcert localhost 127.0.0.1 ::1 192.168.1.99 　 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Nginx证书&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# nginx部署需要crt和key文件，因此也需要通过mkcert生成的文件来进行转换 openssl x509 -in localhost+3.pem -out localhost+3.crt openssl rsa -in localhost+3-key.pem -out localhost+3.key   # 将生成的这两个文件，复制到nginx的conf目录下，并配置nginx.conf  #请填写证书文件的相对路径或绝对路径 ssl_certificate conf.d/maruifu_cn/ssl/localhost+3.crt; #请填写私钥文件的相对路径或绝对路径 ssl_certificate_key conf.d/maruifu_cn/ssl/localhost+3.key; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 18 Apr 2023 09:27:18 GMT</pubDate>
    </item>
    <item>
      <title>jquery操作select（取值，设置选中）</title>
      <link>https://maruifu.cn/article/278</link>
      <content:encoded>&lt;h2&gt;基础取值问题&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;&lt;code&gt;例如&amp;lt;select class=&amp;quot;selector&amp;quot;&amp;gt;&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;# 设置value为pxx的项选中 $(&amp;quot;.selector&amp;quot;).val(&amp;quot;pxx&amp;quot;);  # 设置text为pxx的项选中 $(&amp;quot;.selector&amp;quot;).find(&amp;quot;option:contains('pxx')&amp;quot;).attr(&amp;quot;selected&amp;quot;,true); &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;$(&amp;quot;.selector&amp;quot;).find(&amp;quot;option[text='pxx']&amp;quot;).attr(&amp;quot;selected&amp;quot;,true);&lt;/code&gt;这种写法是错误的， input支持这种获取属性值的写法：&lt;code&gt;input[text='pxx']&lt;/code&gt;，select中需要&lt;code&gt;option:contains('pxx')&lt;/code&gt;这样获取。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;这里有一个中括号的用法，中括号里的等号的前面是属性名称，不用加引号。很多时候，中括号的运用可以使得逻辑变得很简单。&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;# 获取当前选中项的value $(&amp;quot;.selector&amp;quot;).val();  #获取当前选中项的text $(&amp;quot;.selector&amp;quot;).find(&amp;quot;option:selected&amp;quot;).text(); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;select的级联&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;第二个select的值随着第一个select选中的值变化。这在jquery中是非常简单的。&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;如：$(&amp;quot;.selector1&amp;quot;).change(function(){    // 先清空第二个    $(&amp;quot;.selector2&amp;quot;).empty();    // 实际的应用中，这里的option一般都是用循环生成多个了    var option = $(&amp;quot;&amp;lt;option&amp;gt;&amp;quot;).val(1).text(&amp;quot;pxx&amp;quot;);    $(&amp;quot;.selector2&amp;quot;).append(option); }); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;获取Select选择的Text和Value&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; # 为Select添加事件，当选择其中一项时触发  $(&amp;quot;#select_id&amp;quot;).change(function(){//code...});    # 获取Select选择的Text  var checkText=$(&amp;quot;#select_id&amp;quot;).find(&amp;quot;option:selected&amp;quot;).text();   #  获取Select选择的Value  var checkValue=$(&amp;quot;#select_id&amp;quot;).val();   # 获取Select选择的索引值  var checkIndex=$(&amp;quot;#select_id &amp;quot;).get(0).selectedIndex;   # 获取Select最大的索引值  var maxIndex=$(&amp;quot;#select_id option:last&amp;quot;).attr(&amp;quot;index&amp;quot;);  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;设置Select选择的 Text和Value&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 设置Select索引值为1的项选中 $(&amp;quot;#select_id &amp;quot;).get(0).selectedIndex=1;  # 设置Select的Value值为4的项选中 $(&amp;quot;#select_id &amp;quot;).val(4); # 设置Select的Text值为jQuery的项选中 $(&amp;quot;#select_id option[text='jQuery']&amp;quot;).attr(&amp;quot;selected&amp;quot;, true); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;添加/删除Select的Option项&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 为Select追加一个Option(下拉项) $(&amp;quot;#select_id&amp;quot;).append(&amp;quot;&amp;lt;option value='Value'&amp;gt;Text&amp;lt;/option&amp;gt;&amp;quot;); #为Select插入一个Option(第一个位置) $(&amp;quot;#select_id&amp;quot;).prepend(&amp;quot;&amp;lt;option value='0'&amp;gt;请选择&amp;lt;/option&amp;gt;&amp;quot;);  #删除Select中索引值最大Option(最后一个)  $(&amp;quot;#select_id option:last&amp;quot;).remove();  #删除Select中索引值为0的Option(第一个) $(&amp;quot;#select_id option[index='0']&amp;quot;).remove();  #删除Select中Value='3'的Option $(&amp;quot;#select_id option[value='3']&amp;quot;).remove();  #删除Select中Text='4'的Option $(&amp;quot;#select_id option[text='4']&amp;quot;).remove(); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;radio取值，checkbox取值，select取值，radio选中，checkbox选中，select选中，及其相关&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#获取一组radio被选中项的值  var item = $('input[name=items][checked]').val();   #获取select被选中项的文本  var item = $(&amp;quot;select[name=items] option[selected]&amp;quot;).text();  #select下拉框的第二个元素为当前选中值  $('#select_id')[0].selectedIndex = 1;   # radio单选组的第二个元素为当前选中值  $('input[name=items]').get(1).checked = true;  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;获取值&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#文本框，文本区域 $(&amp;quot;#txt&amp;quot;).attr(&amp;quot;value&amp;quot;)；   #多选框 checkbox $(&amp;quot;#checkbox_id&amp;quot;).attr(&amp;quot;value&amp;quot;)；   #单选组radio $(&amp;quot;input[type=radio][checked]&amp;quot;).val();   #下拉框select $('#sel').val();   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;控制表单元素&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 文本框，文本区域  # 清空内容  $(&amp;quot;#txt&amp;quot;).attr(&amp;quot;value&amp;quot;,''); # 填充内容  $(&amp;quot;#txt&amp;quot;).attr(&amp;quot;value&amp;quot;,'11');  # 多选框checkbox # 不打勾  $(&amp;quot;#chk1&amp;quot;).attr(&amp;quot;checked&amp;quot;,''); # 打勾  $(&amp;quot;#chk2&amp;quot;).attr(&amp;quot;checked&amp;quot;,true); # 判断是否已经打勾  if($(&amp;quot;#chk1&amp;quot;).attr('checked')==undefined)  # 单选组 radio # 设置value=2的项目为当前选中项  $(&amp;quot;input[type=radio]&amp;quot;).attr(&amp;quot;checked&amp;quot;,'2');  # 下拉框 select：   # 设置value=-sel3的项目为当前选中项  $(&amp;quot;#sel&amp;quot;).attr(&amp;quot;value&amp;quot;,'-sel3'); # //添加下拉框的option  $(&amp;quot;&amp;lt;option value='1'&amp;gt;1111&amp;lt;/option&amp;gt;&amp;lt;option value='2'&amp;gt;2222&amp;lt;/option&amp;gt;&amp;quot;).appendTo(&amp;quot;#sel&amp;quot;) # 清空下拉框 $(&amp;quot;#sel&amp;quot;).empty()； &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 06 Apr 2023 07:31:00 GMT</pubDate>
    </item>
    <item>
      <title>前端项目运行常见问题</title>
      <link>https://maruifu.cn/article/277</link>
      <content:encoded>&lt;h2&gt;npm install时 node-sass npm ERR command failed问题解决&lt;/h2&gt; &lt;h3&gt;查找对应node版本&lt;/h3&gt; &lt;p&gt;可能是node.js的版本和node-sass的版本不合,下一个符合node-sass版本的node.js&lt;/p&gt; &lt;p&gt;查看需要的node-sass的版本 在&lt;code&gt;package.json&lt;/code&gt;文件中找到&lt;code&gt;node-sass&amp;quot;: &amp;quot;^版本号&amp;quot;&lt;/code&gt;，记下版本号&lt;/p&gt; &lt;p&gt;https://nodejs.org/zh-cn/download/releases/&lt;/p&gt; &lt;p&gt;根据node-sass的版本找到合适的node.js版本下载&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;npm官网 https://www.npmjs.com/package/node-sass&lt;/p&gt; &lt;/blockquote&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;NodeJS&lt;/th&gt;&lt;th&gt;Supported node-sass version&lt;/th&gt;&lt;th&gt;Node Module&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Node 19&lt;/td&gt;&lt;td&gt;8.0+&lt;/td&gt;&lt;td&gt;111&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 18&lt;/td&gt;&lt;td&gt;8.0+&lt;/td&gt;&lt;td&gt;108&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 17&lt;/td&gt;&lt;td&gt;7.0+, &amp;lt;8.0&lt;/td&gt;&lt;td&gt;102&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 16&lt;/td&gt;&lt;td&gt;6.0+&lt;/td&gt;&lt;td&gt;93&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 15&lt;/td&gt;&lt;td&gt;5.0+, &amp;lt;7.0&lt;/td&gt;&lt;td&gt;88&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 14&lt;/td&gt;&lt;td&gt;4.14+&lt;/td&gt;&lt;td&gt;83&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 13&lt;/td&gt;&lt;td&gt;4.13+, &amp;lt;5.0&lt;/td&gt;&lt;td&gt;79&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 12&lt;/td&gt;&lt;td&gt;4.12+, &amp;lt;8.0&lt;/td&gt;&lt;td&gt;72&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 11&lt;/td&gt;&lt;td&gt;4.10+, &amp;lt;5.0&lt;/td&gt;&lt;td&gt;67&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 10&lt;/td&gt;&lt;td&gt;4.9+, &amp;lt;6.0&lt;/td&gt;&lt;td&gt;64&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node 8&lt;/td&gt;&lt;td&gt;4.5.3+, &amp;lt;5.0&lt;/td&gt;&lt;td&gt;57&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Node &amp;lt;8&lt;/td&gt;&lt;td&gt;&amp;lt;5.0&lt;/td&gt;&lt;td&gt;&amp;lt;57&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;blockquote&gt; &lt;p&gt;比如说node-sass是4.9.3就找node10,node module是64的&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;卸掉之前装的node-sass&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;npm uninstall node-sass &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装指定版本的node-sass&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;npm install node-sass@指定的版本号 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;找不到node_modules/node-sass/vendor模块的解决办法&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;Error: ENOENT: no such file or directory, scandir ‘…/node_modules/&lt;a href="https://so.csdn.net/so/search?q=node&amp;amp;spm=1001.2101.3001.7020" target="_blank"&gt;node&lt;/a&gt;-sass/vendor’&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;这个错误的意思就是找不到&lt;strong&gt;node_modules/node-sass/vendor&lt;/strong&gt;模块。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npm rebuild node-sass &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;npm 安装 chromedriver 失败的解决办法&lt;/h2&gt; &lt;p&gt;切网络&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 04 Apr 2023 01:14:00 GMT</pubDate>
    </item>
    <item>
      <title>No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'. 解决方案</title>
      <link>https://maruifu.cn/article/276</link>
      <content:encoded>&lt;ol&gt; &lt;li&gt;执行如下命令 &lt;code&gt;/usr/sbin/pkgutil --packages | grep CLTools&lt;/code&gt; 如果没有返回值，说明缺少xcode相关工具包&lt;/li&gt; &lt;li&gt;依此执行如下命令&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;xcode-select --print-path # in my case /Library/Developer/CommandLineTools  # the next line deletes the path returned by the command above sudo rm -rf $(xcode-select --print-path)  # install them (again) if you don't get a default installation prompt xcode-select --install &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# 重新执行步骤一的命令，得出如下结果即可解决 /usr/sbin/pkgutil --packages | grep CLTools com.apple.pkg.CLTools_Executables com.apple.pkg.CLTools_SDK_macOS110 com.apple.pkg.CLTools_SDK_macOS1015 com.apple.pkg.CLTools_macOS_SDK &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 31 Mar 2023 06:37:22 GMT</pubDate>
    </item>
    <item>
      <title>ESXI安装群晖NAS918</title>
      <link>https://maruifu.cn/article/275</link>
      <content:encoded>&lt;h3&gt;创建虚拟机&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219215320646.png" alt="image-20230219215320646" title="image-20230219215320646" /&gt;&lt;/p&gt; &lt;h3&gt;选择名称和操作系统&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-202302192156346994577cebbad7cce8e.png" alt="image-20230219215634699" title="image-20230219215634699" /&gt;&lt;/p&gt; &lt;h3&gt;选择储存&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-2023021921573314670b734f8daffcf75.png" alt="image-20230219215733146" title="image-20230219215733146" /&gt;&lt;/p&gt; &lt;h3&gt;自定义配置&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;CPU和内存根据自己配置选择,只留一个网络适配器 其余都删除&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;注意网络适配器类型选择E1000e，mac地址选自动&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219220229384.png" alt="image-20230219220229384" title="image-20230219220229384" /&gt;&lt;/p&gt; &lt;h3&gt;配置虚拟机&lt;/h3&gt; &lt;p&gt;选中刚才添加虚拟机-&amp;gt;操作-&amp;gt;编辑配置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219220706242.png" alt="image-20230219220706242" title="image-20230219220706242" /&gt;&lt;/p&gt; &lt;h4&gt;添加其他设备&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219220855242.png" alt="image-20230219220855242" title="image-20230219220855242" /&gt;&lt;/p&gt; &lt;h4&gt;添加硬盘&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219220940815.png" alt="image-20230219220940815" title="image-20230219220940815" /&gt;&lt;/p&gt; &lt;p&gt;选中自己命名虚拟机的文件夹然后上载&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219221530582.png" alt="image-20230219221530582" title="image-20230219221530582" /&gt;&lt;/p&gt; &lt;p&gt;分别上载两个文件，但是只会显示一个，然后选择。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219221651652.png" alt="image-20230219221651652" title="image-20230219221651652" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219221849228.png" alt="image-20230219221849228" title="image-20230219221849228" /&gt;&lt;/p&gt; &lt;p&gt;添加新标准硬盘&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219221930623.png" alt="image-20230219221930623" title="image-20230219221930623" /&gt;&lt;/p&gt; &lt;p&gt;然后根据自己配置选择硬盘大小&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219222050495.png" alt="image-20230219222050495" title="image-20230219222050495" /&gt;&lt;/p&gt; &lt;h4&gt;虚拟机选项&lt;/h4&gt; &lt;p&gt;取消勾选 是否为此虚拟机启用UEFI安全引导，不取消会启动不了，最后保存！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219222148110.png" alt="image-20230219222148110" title="image-20230219222148110" /&gt;&lt;/p&gt; &lt;h3&gt;启动电源&lt;/h3&gt; &lt;p&gt;启动后选择第二项SATA启动否则后面会格式化失败！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219222336179.png" alt="image-20230219222336179" title="image-20230219222336179" /&gt;&lt;/p&gt; &lt;h3&gt;安装群晖&lt;/h3&gt; &lt;p&gt;使用synology-assistant 工具进行搜索&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219223432456.png" alt="image-20230219223432456" title="image-20230219223432456" /&gt;&lt;/p&gt; &lt;p&gt;浏览器输入搜索到的IP地址进行安装&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219223611479.png" alt="image-20230219223611479" title="image-20230219223611479" /&gt;&lt;/p&gt; &lt;p&gt;选择安装的镜像&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219223648693.png" alt="image-20230219223648693" title="image-20230219223648693" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-2023021922373481806076e667156825b.png" alt="image-20230219223734818" title="image-20230219223734818" /&gt;&lt;/p&gt; &lt;h3&gt;安装成功&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219224015649.png" alt="image-20230219224015649" title="image-20230219224015649" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;如果安装失败,请断网后重试！&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;安装VMware Tools&lt;/h2&gt; &lt;p&gt;套件中心-&amp;gt;设置-&amp;gt;套件来源-&amp;gt;新增矿神来源&lt;/p&gt; &lt;pre&gt;&lt;code&gt;https://spk7.imnks.com/ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219205923181.png" alt="image-20230219205923181" title="image-20230219205923181" /&gt;&lt;/p&gt; &lt;p&gt;社群搜索&lt;code&gt;vmware&lt;/code&gt; 安装VMware Tools&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219210208897.png" alt="image-20230219210208897" title="image-20230219210208897" /&gt;&lt;/p&gt; &lt;p&gt;安装成功后esxi可正常显示 主机名称IP地址及其配置信息&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219210542002.png" alt="image-20230219210542002" title="image-20230219210542002" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;如果出现还原情况，安装时切记断网操作是因为现在版本的群晖系统存在联网检测&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sun, 19 Feb 2023 14:42:00 GMT</pubDate>
    </item>
    <item>
      <title>VMware-ESXI-6.7修改静态IP地址和主机名称以及许可证</title>
      <link>https://maruifu.cn/article/273</link>
      <content:encoded>&lt;h2&gt;启用Shell&lt;/h2&gt; &lt;p&gt;首先登陆网页控制台，在主界面点击“主机”–&amp;gt;“服务”–&amp;gt;“启用安全shell”&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219153121702.png" alt="image-20230219153121702" title="image-20230219153121702" /&gt;&lt;/p&gt; &lt;h2&gt;连接esxi主机&lt;/h2&gt; &lt;p&gt;用shell工具连接esxi主机，在命令行输入“dcui”，就会出现显示器的控制台，由于没有显示器，所以就用这种方式改比较方便，如果有显示器，直接在显示器输出的界面上改就行，我这里由于是命令行，所以是黑白界面，显示器上会显示黄黑界面&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219153953802.png" alt="image-20230219153953802" title="image-20230219153953802" /&gt;&lt;/p&gt; &lt;h2&gt;设置ESXI&lt;/h2&gt; &lt;p&gt;按f2进入以下界面，输入用户名和密码回车&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219154055982.png" alt="image-20230219154055982" title="image-20230219154055982" /&gt;&lt;/p&gt; &lt;h2&gt;修改静态IP和主机名&lt;/h2&gt; &lt;p&gt;选择“configure management network”回车&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219154154683.png" alt="image-20230219154154683" title="image-20230219154154683" /&gt;&lt;/p&gt; &lt;p&gt;选择“ipv4 configuration”回车，选择set那一行，空格是选中，把ipv4地址改成自己想要的地址（1-255之间都可以），网段一定要和自己的一样，不然下次你就连接不上了，改完之后回车确定。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-202302191542339001ea54a0d0132e5a8.png" alt="image-20230219154233900" title="image-20230219154233900" /&gt;&lt;/p&gt; &lt;p&gt;在选择“dns configuration”回车，把dns服务器指定一下，我这里用的“114.114.114.114”，备用的是“8.8.8.8”，改完也是回车确定&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/19/image-20230219154439478.png" alt="image-20230219154439478" title="image-20230219154439478" /&gt;&lt;/p&gt; &lt;p&gt;然后都修改好之后，按ESC键退出，会提示你是否重启网络立即生效，按“Y”键就可以立即生效，然后你现在的命令行就会没有反应，用你修改的ip新建一个重新连接即可。&lt;/p&gt; &lt;h2&gt;许可证&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 适用版本：VMware vSphere 6 Enterprise Plus # 激活密钥 0A65P-00HD0-3Z5M1-M097M-22P7H &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 19 Feb 2023 07:47:00 GMT</pubDate>
    </item>
    <item>
      <title>ES -IK分词器分词、停用词基于API实现热更新</title>
      <link>https://maruifu.cn/article/272</link>
      <content:encoded>&lt;h2&gt;官方介绍&lt;/h2&gt; &lt;p&gt;在&lt;a href="https://github.com/medcl/elasticsearch-analysis-ik" target="_blank"&gt;ik的github&lt;/a&gt;有关于热更新的介绍&lt;/p&gt; &lt;h3&gt;字典配置&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;IKAnalyzer.cfg.xml` can be located at `{conf}/analysis-ik/config/IKAnalyzer.cfg.xml` or `{plugins}/elasticsearch-analysis-ik-*/config/IKAnalyzer.cfg.xml &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;!DOCTYPE properties SYSTEM &amp;quot;http://java.sun.com/dtd/properties.dtd&amp;quot;&amp;gt; &amp;lt;properties&amp;gt;  &amp;lt;comment&amp;gt;IK Analyzer 扩展配置&amp;lt;/comment&amp;gt;  &amp;lt;!--用户可以在这里配置自己的扩展字典 --&amp;gt;  &amp;lt;entry key=&amp;quot;ext_dict&amp;quot;&amp;gt;custom/mydict.dic;custom/single_word_low_freq.dic&amp;lt;/entry&amp;gt;   &amp;lt;!--用户可以在这里配置自己的扩展停止词字典--&amp;gt;  &amp;lt;entry key=&amp;quot;ext_stopwords&amp;quot;&amp;gt;custom/ext_stopword.dic&amp;lt;/entry&amp;gt;   &amp;lt;!--用户可以在这里配置远程扩展字典 --&amp;gt;  &amp;lt;entry key=&amp;quot;remote_ext_dict&amp;quot;&amp;gt;location&amp;lt;/entry&amp;gt;   &amp;lt;!--用户可以在这里配置远程扩展停止词字典--&amp;gt;  &amp;lt;entry key=&amp;quot;remote_ext_stopwords&amp;quot;&amp;gt;http://xxx.com/xxx.dic&amp;lt;/entry&amp;gt; &amp;lt;/properties&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;热更新 IK 分词使用方法&lt;/h3&gt; &lt;p&gt;目前该插件支持热更新 IK 分词，通过上文在 IK 配置文件中提到的如下配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;!--用户可以在这里配置远程扩展字典 --&amp;gt;  &amp;lt;entry key=&amp;quot;remote_ext_dict&amp;quot;&amp;gt;location&amp;lt;/entry&amp;gt;   &amp;lt;!--用户可以在这里配置远程扩展停止词字典--&amp;gt;  &amp;lt;entry key=&amp;quot;remote_ext_stopwords&amp;quot;&amp;gt;location&amp;lt;/entry&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中 &lt;code&gt;location&lt;/code&gt; 是指一个 url，比如 &lt;code&gt;http://yoursite.com/getCustomDict&lt;/code&gt;，该请求只需满足以下两点即可完成分词热更新。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;该 http 请求需要返回两个头部(header)，一个是 &lt;code&gt;Last-Modified&lt;/code&gt;，一个是 &lt;code&gt;ETag&lt;/code&gt;，这两者都是字符串类型，只要有一个发生变化，该插件就会去抓取新的分词进而更新词库。&lt;/li&gt; &lt;li&gt;该 http 请求返回的内容格式是一行一个分词，换行符用 &lt;code&gt;\n&lt;/code&gt; 即可。&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;说明&lt;/h3&gt; &lt;p&gt;Last-Modified是上次更新时间 ETag是实体标签（Entity Tag）的缩写，根据实体内容（文本数据）生成的一段hash字符串，可以通过它的标识数据的修改状态，当数据发生改变时，ETag也随之改变&lt;/p&gt; &lt;h2&gt;思路&lt;/h2&gt; &lt;p&gt;分词文件可以存放在一个文本文件中，注意文件需要时UTF-8编码格式的。放在nginx或者其他简易http server下，当文件修改时，http  server会在客户端请求该文件时自动返回相应的Last-Modified和ETag。&lt;/p&gt; &lt;p&gt;另外可以做一个工具来从业务系统提取相关词汇，再更新到这个文件中&lt;/p&gt; &lt;h3&gt;如何设置ETag的值&lt;/h3&gt; &lt;p&gt;首先ETag要满足以下三个条件： 1、当文件内容改变时，ETag值跟着改变 2、计算简单，不会特别消耗CPU（因此就不能使用MD5、SHA128、SHA256等算法） 3、必须支持横向扩展，也就是在不同的服务器节点上生成的ETag是一样的&lt;/p&gt; &lt;p&gt;&lt;strong&gt;参考Nginx中ETag的生成：&lt;/strong&gt; 由Last-Modified和content_length表示为16进制组合而成&lt;/p&gt; &lt;pre&gt;&lt;code&gt;etag = '&amp;quot;' + Long.toHexString(lastModified) + '-' + Long.toHexString(contentLength) + '&amp;quot;'; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;有了Last-Modified为什么还需要ETag&lt;/h3&gt; &lt;p&gt;1、原因就是因为某些服务器如果不能精确的获取到数据的最后修改时间的话，那么就无法通过Last-Modified来判断数据是否有更新了，就会导致词库更新不及时，出现延迟的问题。&lt;/p&gt; &lt;p&gt;2、Last-Modified一般只能精确到秒，如果数据更新的比较频繁的话，会导致识别不到更新，当然这点在IK分词器中不存在，因为IK分词器的热更新默认是一分钟获取一次。&lt;/p&gt; &lt;p&gt;3、如果我在一分钟内改了文件，发现改错了，又改回来了，那么这个时间虽然修改时间变了，但是因为内容没变，我是不希望更新它的。&lt;/p&gt; &lt;p&gt;综上所述，我们需要一个ETag来帮助我们判断是否更新了文本内容，同时ETag还需要根据文本内容来产生。&lt;/p&gt; &lt;h2&gt;代码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; @RequestMapping(&amp;quot;updateHotWord&amp;quot;)     public void hotWord1(HttpServletResponse response, @RequestParam Integer type) throws IOException {         List&amp;lt;String&amp;gt; dict = new ArrayList();         if(type == 1){             //TODO 直接读取数据库维护 热词         }else{             //TODO 直接读取数据库维护 停止词         }         String str = dict.parallelStream().collect(Collectors.joining(&amp;quot;\n&amp;quot;));         Long lastModified = Instant.now().toEpochMilli();         String etag =   Long.toHexString(str.length());         response.setHeader(&amp;quot;Last-Modified&amp;quot;,String.valueOf(lastModified));         response.setHeader(&amp;quot;ETag&amp;quot;,etag);         response.setContentType(&amp;quot;text/plain;charset=utf-8&amp;quot;);         OutputStream out = response.getOutputStream();         out.write(str.getBytes());         out.flush();     }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置&lt;/h2&gt; &lt;p&gt;修改ik分词器配置文件IKAnalyzer.cfg.xml&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;properties&amp;gt;         &amp;lt;comment&amp;gt;IK Analyzer 扩展配置&amp;lt;/comment&amp;gt;         &amp;lt;!--用户可以在这里配置自己的扩展字典 --&amp;gt;         &amp;lt;entry key=&amp;quot;ext_dict&amp;quot;&amp;gt;custom/my_extend.dic&amp;lt;/entry&amp;gt;          &amp;lt;!--用户可以在这里配置自己的扩展停止词字典--&amp;gt;         &amp;lt;entry key=&amp;quot;ext_stopwords&amp;quot;&amp;gt;&amp;lt;/entry&amp;gt;         &amp;lt;!--用户可以在这里配置远程扩展字典 --&amp;gt;         &amp;lt;entry key=&amp;quot;remote_ext_dict&amp;quot;&amp;gt;http://localhost:8080/hot/updateHotWord?type=0&amp;lt;/entry&amp;gt;         &amp;lt;!--用户可以在这里配置远程扩展停止词字典--&amp;gt;          &amp;lt;entry key=&amp;quot;remote_ext_stopwords&amp;quot;&amp;gt;http://localhost:8080/hot/updateHotWord?type=1&amp;lt;/entry&amp;gt; &amp;lt;/properties&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;重启es 会发现启动时会显示读取的分词内容&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sun, 12 Feb 2023 14:43:00 GMT</pubDate>
    </item>
    <item>
      <title>idea导出非maven项目war包</title>
      <link>https://maruifu.cn/article/271</link>
      <content:encoded>&lt;h2&gt;选择界面&lt;/h2&gt; &lt;p&gt;File-&amp;gt;Project Structure-&amp;gt;Artifacts-&amp;gt;Add-&amp;gt;Java Web Applicatiion:Archive&lt;/p&gt; &lt;h2&gt;修改WAR包名称和存放路径&lt;/h2&gt; &lt;p&gt;Name：&lt;/p&gt; &lt;p&gt;Output dirctory：&lt;/p&gt; &lt;h2&gt;选择输出根信息&lt;/h2&gt; &lt;p&gt;Available Elements 模块下全选右键点击 “Put into Output Root”。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/12/2023-02-12-10.16.01.png" alt="截屏2023-02-12 下午10.16.01" title="截屏2023-02-12 下午10.16.01" /&gt;&lt;/p&gt; &lt;h2&gt;选择输出JSP文件&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/12/2023-02-12-10.18.42.png" alt="截屏2023-02-12 下午10.18.42" title="截屏2023-02-12 下午10.18.42" /&gt;&lt;/p&gt; &lt;h2&gt;打包项目成war&lt;/h2&gt; &lt;p&gt;Build-&amp;gt;Build Artifacts-&amp;gt;Build&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/02/12/2023-02-12-10.21.46.png" alt="截屏2023-02-12 下午10.21.46" title="截屏2023-02-12 下午10.21.46" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 12 Feb 2023 14:22:35 GMT</pubDate>
    </item>
    <item>
      <title>SpringBoot + MDC 实现全链路调用日志跟踪</title>
      <link>https://maruifu.cn/article/270</link>
      <content:encoded>&lt;h2&gt;MDC 介绍&lt;/h2&gt; &lt;p&gt;MDC（Mapped Diagnostic Context，映射调试上下文）是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。&lt;strong&gt;MDC&lt;/strong&gt; 可以看成是一个与&lt;strong&gt;当前线程绑定的哈希表&lt;/strong&gt;，可以往其中添加键值对。MDC 中包含的内容可以&lt;strong&gt;被同一线程中执行的代码所访问&lt;/strong&gt;。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时，只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说，通常是在请求被处理的最开始保存这些数据。&lt;/p&gt; &lt;h2&gt;API 说明&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;clear() =&amp;gt; 移除所有 MDC&lt;/li&gt; &lt;li&gt;get (String key) =&amp;gt; 获取当前线程 MDC 中指定 key 的值&lt;/li&gt; &lt;li&gt;getContext() =&amp;gt; 获取当前线程 MDC 的 MDC&lt;/li&gt; &lt;li&gt;put(String key, Object o) =&amp;gt; 往当前线程的 MDC 中存入指定的键值对&lt;/li&gt; &lt;li&gt;remove(String key) =&amp;gt; 删除当前线程 MDC 中指定的键值对&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;MDC 使用&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;Constants.TRACE_ID  = &amp;quot;traceId&amp;quot;&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;添加拦截器&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public class LogInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         //如果有上层调用就用上层的ID         String traceId = request.getHeader(Constants.TRACE_ID);         if (traceId == null) {             traceId = TraceIdUtil.getTraceId();         }          MDC.put(Constants.TRACE_ID, traceId);         return true;     }      @Override     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)             throws Exception {     }      @Override     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)             throws Exception {         //调用结束后删除         MDC.remove(Constants.TRACE_ID);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改日志格式&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!-- 日志输出格式 --&amp;gt; &amp;lt;property name=&amp;quot;log.pattern&amp;quot; value=&amp;quot;[TraceId:%X{traceId}] %d{HH🇲🇲ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n&amp;quot;/&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;重点是 %X{traceId}，traceId 和 MDC 中的键名称一致。&lt;/p&gt; &lt;p&gt;简单使用就这么容易，但是在有些情况下 traceId 将获取不到。&lt;/p&gt; &lt;h2&gt;常见问题&lt;/h2&gt; &lt;h3&gt;子线程日志打印丢失 traceId&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;   //获取traceId    Map&amp;lt;String, String&amp;gt; mdcContextMap = MDC.getCopyOfContextMap();    return  () -&amp;gt;{      //添加到子线程中      MDC.setContextMap(mdcContextMap);      System.out.println(&amp;quot;你好呀！&amp;quot;);    }                 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 10 Jan 2023 07:05:00 GMT</pubDate>
    </item>
    <item>
      <title>Java 生成透明图片</title>
      <link>https://maruifu.cn/article/269</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;设置图片透明&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; import javax.swing.*; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException;  /**  * 图片处理工具类  *  * @author maruifu  */ public class ImageUtils1 {       private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);       public static void main(String[] args) {         String text = &amp;quot;我是小马哥&amp;quot;;         String filePath = &amp;quot;/Users/maruifu/Desktop/1.png&amp;quot;;         //生成指定文字透明图片         createImage(text,filePath,&amp;quot;宋体&amp;quot;,45);         //设置指定图片透明         //setColorInRange(path,path);     }           /**      * 动态获取文字宽高      * @param text 文字      * @param font 字体      * @return      */     private static int[] getWidthAndHeight(String text, Font font) {         Rectangle2D r = font.getStringBounds(text, new FontRenderContext(                 AffineTransform.getScaleInstance(1, 1), false, false));         int unitHeight = (int) Math.floor(r.getHeight());         // 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度         int width = (int) Math.round(r.getWidth()) + 1;         // 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度         int height = unitHeight + 3;         return new int[]{width, height};     }       /**      * 生成指定文字透明图片      * @param text 文字内容 &amp;quot;我是小马哥&amp;quot;      * @param filePath 生成地址 &amp;quot;/Users/maruifu/Desktop/123.png&amp;quot;      * @param fontName 字体名称 &amp;quot;宋体&amp;quot;      * @param fontSize 字体大小 45      */     public static void createImage(String text, String filePath,String fontName,int fontSize)  {         ImageOutputStream imageOutputStream = null;         try {             Font font = new Font(fontName, Font.BOLD,fontSize);             // 获取font的样式应用在输出内容上整个的宽高             int[] arr = getWidthAndHeight(text, font);             // 设置背景宽高             int width = arr[0];             int height = arr[1];             BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);             // 获取图形上下文对象             Graphics2D graphics = (Graphics2D)image.getGraphics();             graphics.clearRect(0, 0, width, height);             // 填充             graphics.fillRect(0, 0, width, height);             // 设定字体大小及样式             graphics.setFont(font);             // 字体颜色             graphics.setColor(Color.BLACK);             // 描绘字符串             graphics.drawString(text, 0,   font.getSize());             // 图片透明度             setTransparency(image, graphics);             File jpgFile = new File(filePath);             if(!jpgFile.exists()) {                 jpgFile.createNewFile();             }             // 创建图片输出流对象，基于文件对象             imageOutputStream = ImageIO.createImageOutputStream(jpgFile);             // 写入             ImageIO.write(image, FilenameUtils.getExtension(filePath),imageOutputStream);         } catch (IOException e) {             log.error(&amp;quot;生成图片失败{}&amp;quot;,e);         } finally {             // 关闭流             IOUtils.closeQuietly(imageOutputStream);         }      }      private static void setTransparency(BufferedImage image, Graphics2D graphics) {         int alpha = 0;         // 外层遍历是Y轴的像素         for (int y = image.getMinY(); y &amp;lt; image.getHeight(); y++) {             // 内层遍历是X轴的像素             for (int x = image.getMinX(); x &amp;lt; image.getWidth(); x++) {                 int rgb = image.getRGB(x, y);                 // 对当前颜色判断是否在指定区间内                 if (colorInRange(rgb)) {                     alpha = 0;                 } else {                     // 设置为不透明                     alpha = 255;                 }                 // #AARRGGBB 最前两位为透明度                 rgb = (alpha &amp;lt;&amp;lt; 24) | (rgb &amp;amp; 0x00ffffff);                 image.setRGB(x, y, rgb);             }         }         // 绘制设置了RGB的新图片         graphics.drawImage(image, 0, 0, null);         //释放画笔         graphics.dispose();     }       /**      * 设置指定图片透明      * @param sourcePath 源文件地址      * @param targetPath 新文件地址      */     public static void setColorInRange(String sourcePath,String targetPath ) {         try {             BufferedImage image = ImageIO.read(new File(sourcePath));             // 高度和宽度             int height = image.getHeight();             int width = image.getWidth();              // 生产背景透明和内容透明的图片             ImageIcon imageIcon = new ImageIcon(image);             BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);             // 获取画笔             Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics();             // 绘制Image的图片             g2D.drawImage(imageIcon.getImage(), 0, 0, null);             // 图片透明             setTransparency(bufferedImage, g2D);             // 生成图片为PNG             ImageIO.write(bufferedImage, FilenameUtils.getExtension(sourcePath), new File(targetPath));         } catch (IOException e) {             log.error(&amp;quot;设置图片透明失败{}&amp;quot;,e);         }      }         /**      * 判断是背景还是内容      * @param color      * @return      */     public static boolean colorInRange(int color) {         // 色差范围0~255         int colorRange = 210;         // 获取color(RGB)中R位         int red = (color &amp;amp; 0xff0000) &amp;gt;&amp;gt; 16;         // 获取color(RGB)中G位         int green = (color &amp;amp; 0x00ff00) &amp;gt;&amp;gt; 8;         // 获取color(RGB)中B位         int blue = (color &amp;amp; 0x0000ff);         // 通过RGB三分量来判断当前颜色是否在指定的颜色区间内         if (red &amp;gt;= colorRange &amp;amp;&amp;amp; green &amp;gt;= colorRange &amp;amp;&amp;amp; blue &amp;gt;= colorRange) {             return true;         }         return false;     } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 30 Dec 2022 02:05:00 GMT</pubDate>
    </item>
    <item>
      <title>使用spring-boot-starter-data-elasticsearch 设置了long，为什么却变成了keyword类型</title>
      <link>https://maruifu.cn/article/268</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;实体类定义属性&lt;code&gt;book_id&lt;/code&gt;为&lt;code&gt;Long&lt;/code&gt;类型，但在调用 &lt;code&gt;spring-data-elasticsearch:2.5.14.RELEASE&lt;/code&gt;中的createMapping()&lt;code&gt;方法时却被转换成了&lt;/code&gt;keyword`类型&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;查看&lt;code&gt;createMapping&lt;/code&gt;方法，源码可以发现最终调用最下边的重载方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt; IndexOperations.createMapping();   AbstractDefaultIndexOperations.createMapping();  AbstractDefaultIndexOperations.createMapping(Class&amp;lt;?&amp;gt; clazz);  AbstractDefaultIndexOperations.buildMapping(Class&amp;lt;?&amp;gt; clazz) //构建属性映射 MappingBuilder.buildPropertyMapping(Class&amp;lt;?&amp;gt; clazz) // 具体的properties解析，为根对象非nested对象 MappingBuilder.mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity&amp;lt;?&amp;gt; entity, boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping);           MappingBuilder.buildPropertyMapping(XContentBuilder builder, boolean isRootObject, ElasticsearchPersistentProperty property)  //判断是否是带有@Id注解或者是字段名是否是 id或者document ，如果满足条件这是主键字段 ElasticsearchPersistentProperty.isIdProperty() //如果是主键字段则类型设置为keyword MappingBuilder.applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)   &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 27 Dec 2022 08:41:16 GMT</pubDate>
    </item>
    <item>
      <title>SpringBoot Security密码加盐</title>
      <link>https://maruifu.cn/article/267</link>
      <content:encoded>&lt;h2&gt;修改加密和验证方法&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;    /**      * 生成BCryptPasswordEncoder密码      *      * @param password 密码      * @param salt 盐值      * @return 加密字符串      */     public static String encryptPassword(String password,String salt) {         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();          return passwordEncoder.encode(password + salt);     }      /**      * 判断密码是否相同      *      * @param rawPassword     真实密码      * @param encodedPassword 加密后字符      * @param salt 盐值      * @return 结果      */     public static boolean matchesPassword(String rawPassword, String encodedPassword,String salt) {         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();         return passwordEncoder.matches(rawPassword + salt, encodedPassword);     } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;自定义 DaoAuthenticationProvider&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;import com.maruifu.common.core.domain.model.LoginUser; import com.maruifu.common.utils.DateUtils; import com.maruifu.common.utils.SecurityUtils; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.Authentication;  /**  * 身份验证提供者  * @author maruifu  */  public class JwtAuthenticationProvider extends DaoAuthenticationProvider {       @Override     public Authentication authenticate(Authentication authentication) throws AuthenticationException {         // 可以在此处覆写整个登录认证逻辑         return super.authenticate(authentication);     }       /**      * 重写加盐后验证逻辑      * @param userDetails      * @param authentication      * @throws AuthenticationException      */     @Override     protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {         if (authentication.getCredentials() == null) {             this.logger.debug(&amp;quot;Failed to authenticate since no credentials provided&amp;quot;);             throw new BadCredentialsException(this.messages.getMessage(&amp;quot;AbstractUserDetailsAuthenticationProvider.badCredentials&amp;quot;, &amp;quot;Bad credentials&amp;quot;));         } else {             String presentedPassword = authentication.getCredentials().toString();             LoginUser loginUser =  (LoginUser)userDetails ;             if (!SecurityUtils.matchesPassword(presentedPassword, userDetails.getPassword(), DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS,loginUser.getUser().getCreateTime()))) {                 this.logger.debug(&amp;quot;Failed to authenticate since password does not match stored value&amp;quot;);                 throw new BadCredentialsException(this.messages.getMessage(&amp;quot;AbstractUserDetailsAuthenticationProvider.badCredentials&amp;quot;, &amp;quot;Bad credentials&amp;quot;));             }         }     }  }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;注册到ProciderManager中&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; import com.maruifu.framework.security.handle.JwtAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService;  /**  * spring security配置  *  * @author maruifu  */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig1 extends WebSecurityConfigurerAdapter {     /**      * 自定义用户认证逻辑      */     @Autowired     private UserDetailsService userDetailsService;       /**      * 解决 无法直接注入 AuthenticationManager      * 重写 加盐后验证逻辑      *      * @return      */     @Bean     @Override     public AuthenticationManager authenticationManagerBean(){         JwtAuthenticationProvider provider=new JwtAuthenticationProvider();         provider.setUserDetailsService(userDetailsService);         ProviderManager manager=new ProviderManager(provider);         return manager;     }           ......省略configure方法  }  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 24 Dec 2022 17:41:07 GMT</pubDate>
    </item>
    <item>
      <title>Spring中@Conditional通过条件来控制bean的注册</title>
      <link>https://maruifu.cn/article/266</link>
      <content:encoded>&lt;h2&gt;Spring对配置类的处理主要分为2个阶段&lt;/h2&gt; &lt;h3&gt;配置类解析阶段&lt;/h3&gt; &lt;p&gt;会得到一批配置类的信息，和一些需要注册的bean&lt;/p&gt; &lt;h3&gt;bean注册阶段&lt;/h3&gt; &lt;p&gt;将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中&lt;/p&gt; &lt;p&gt;看一下什么是配置类,类中有下面任意注解之一的就属于配置类：&lt;/p&gt; &lt;p&gt;类上有@Compontent注解，@Configuration注解，@CompontentScan注解，@Import注解，@ImportResource注解以及类中有@Bean标注的方法 的都是配置类&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;判断一个类是不是一个配置类，是否的是下面这个方法，有兴趣的可以看一下：&lt;/p&gt; &lt;p&gt;org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;spring中处理这2个过程会循环进行，直到完成所有配置类的解析及所有bean的注册。&lt;/p&gt; &lt;h2&gt;Spring对配置类处理过程&lt;/h2&gt; &lt;h3&gt;源码位置&lt;/h3&gt; &lt;p&gt;&lt;code&gt;org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;整个过程大致的过程&lt;/h3&gt; &lt;p&gt;通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器&lt;/p&gt; &lt;p&gt;spring对传入的多个配置类进行解析&lt;/p&gt; &lt;p&gt;配置类解析阶段：这个过程就是处理配置类上面6中注解的过程，此过程中又会发现很多新的配置类，比如@Import导入的一批新的类刚好也符合配置类，而被@CompontentScan扫描到的一些类刚好也是配置类；此时会对这些新产生的配置类进行同样的过程解析&lt;/p&gt; &lt;p&gt;bean注册阶段：配置类解析后，会得到一批配置类和一批需要注册的bean，此时spring容器会将这批配置类作为bean注册到spring容器，同样也会将这批需要注册的bean注册到spring容器&lt;/p&gt; &lt;p&gt;经过上面第3个阶段之后，spring容器中会注册很多新的bean，这些新的bean中可能又有很多新的配置类&lt;/p&gt; &lt;p&gt;Spring从容器中将所有bean拿出来，遍历一下，会过滤得到一批未处理的新的配置类，继续交给第3步进行处理&lt;/p&gt; &lt;p&gt;step3到step6，这个过程会经历很多次，直到完成所有配置类的解析和bean的注册&lt;/p&gt; &lt;p&gt;从上面过程中可以了解到：&lt;/p&gt; &lt;p&gt;可以在配置类上面加上@Conditional注解，来控制是否需要解析这个配置类，配置类如果不被解析，那么这个配置上面6种注解的解析都会被跳过&lt;/p&gt; &lt;p&gt;可以在被注册的bean上面加上@Conditional注解，来控制这个bean是否需要注册到spring容器中&lt;/p&gt; &lt;p&gt;如果配置类不会被注册到容器，那么这个配置类解析所产生的所有新的配置类及所产生的所有新的bean都不会被注册到容器&lt;/p&gt; &lt;p&gt;一个配置类被spring处理有2个阶段：配置类解析阶段、bean注册阶段（将配置类作为bean被注册到spring容器)。&lt;/p&gt; &lt;p&gt;如果将Condition接口的实现类作为配置类上@Conditional中，那么这个条件会对两个阶段都有效，此时通过Condition是无法精细的控制某个阶段的，如果想控制某个阶段，比如可以让他解析，但是不能让他注册，此时就就需要用到另外一个接口了：ConfigurationCondition&lt;/p&gt; &lt;h3&gt;ConfigurationCondition接口&lt;/h3&gt; &lt;p&gt;相对于Condition接口多了一个getConfigurationPhase方法，用来指定条件判断的阶段，是在解析配置类的时候过滤还是在创建bean的时候过滤。&lt;/p&gt; &lt;h3&gt;Conditional使用的3步骤&lt;/h3&gt; &lt;p&gt;自定义一个类，实现Condition或ConfigurationCondition接口，实现matches方法&lt;/p&gt; &lt;p&gt;在目标对象上使用@Conditional注解，并指定value的指为自定义的Condition类型&lt;/p&gt; &lt;p&gt;启动spring容器加载资源，此时@Conditional就会起作用了&lt;/p&gt; &lt;h2&gt;阻止配置类的处理&lt;/h2&gt; &lt;p&gt;在配置类上面使用@Conditional，这个注解的value指定的Condition当有一个为false的时候，spring就会跳过处理这个配置类。&lt;/p&gt; &lt;p&gt;自定义一个Condition类：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.type.AnnotatedTypeMetadata;   /**  * 自定义控制器  *  * @author maruifu  * @date 2022-12-07  */ @Configuration public class DslLogCondition implements Condition {      @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         // 读取配置文件application.yml中 spring.elasticsearch.dslLog: true 配置         YamlPropertiesFactoryBean y=new YamlPropertiesFactoryBean();         y.setResources(new ClassPathResource(&amp;quot;application.yml&amp;quot;));         return (Boolean) y.getObject().get(&amp;quot;spring.elasticsearch.dslLog&amp;quot;);      } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;matches方法内部我们可以随意发挥，此处为了演示效果读取的配置文件。&lt;/p&gt; &lt;p&gt;来个配置类，在配置类上面使用上面这个条件，此时会让配置类失效，如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.elasticsearch.client.RestHighLevelClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.RestClients;  /**  * 用于打印dsl语句  */  @Configuration public class EsConfig {      @Value(&amp;quot;${spring.elasticsearch.rest.uris}&amp;quot;)     private String elasticsearchHost;      @Conditional(DslLogCondition.class)     @Bean(destroyMethod = &amp;quot;close&amp;quot;)     public RestHighLevelClient restClient() {         ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(elasticsearchHost).build();         RestHighLevelClient client = RestClients.create(clientConfiguration).rest();          return client;      } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;1：使用了自定义的条件类&lt;/p&gt; &lt;p&gt;2：通过@Bean标注这restClient这个方法，如果这个配置类成功解析，会将restClient方法的返回值作为bean注册到spring容器&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;bean不存在的时候才注册&lt;/h2&gt; &lt;p&gt;IService接口有两个实现类Service1和Service1，这两个类会放在2个配置类中通过@Bean的方式来注册到容器，此时我们想加个限制，只允许有一个IService类型的bean被注册到容器。&lt;/p&gt; &lt;p&gt;可以在@Bean标注的2个方法上面加上条件限制，当容器中不存在IService类型的bean时，才将这个方法定义的bean注册到容器，下面来看代码实现。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.core.type.AnnotatedTypeMetadata;   import java.util.Map;   public class OnMissingBeanCondition implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         //获取bean工厂         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();         //从容器中获取IService类型bean         Map&amp;lt;String, IService&amp;gt; serviceMap = beanFactory.getBeansOfType(IService.class);         //判断serviceMap是否为空         return serviceMap.isEmpty();     }   &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;上面matches方法中会看容器中是否存在IService类型的bean，不存在的时候返回true&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;来一个配置类负责注册Service1到容器&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration;   @Configuration public class BeanConfig1 {     @Conditional(OnMissingBeanCondition.class) //@1     @Bean     public IService service1() {         return new Service1();     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再来一个配置类负责注册Service2到容器&lt;/p&gt; &lt;pre&gt;&lt;code&gt; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration;   @Configuration public class BeanConfig2 {     @Conditional(OnMissingBeanCondition.class)//@1     @Bean     public IService service2() {         return new Service2();     }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;来一个总的配置类，导入另外2个配置类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import;   @Configuration @Import({BeanConfig1.class,BeanConfig2.class})  public class MainConfig { }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;根据环境选择配置类&lt;/h2&gt; &lt;p&gt;平常我们做项目的时候，有开发环境、测试环境、线上环境，每个环境中有些信息是不一样的，比如数据库的配置信息，下面我们来模拟不同环境中使用不同的配置类来注册不同的bean&lt;/p&gt; &lt;p&gt;自定义一个条件的注解&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Conditional;   import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;   @Conditional(EnvCondition.class) //@1 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EnvConditional {     //环境(测试环境、开发环境、生产环境)     enum Env { //@2         TEST, DEV, PROD     }       //环境     Env value() default Env.DEV; //@3 } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：注意这个注解比较特别，这个注解上面使用到了@Conditional注解，这个地方使用到了一个自定义Conditione类：EnvCondition&lt;/p&gt; &lt;p&gt;@2：枚举，表示环境，定义了3个环境&lt;/p&gt; &lt;p&gt;@3：这个参数用指定环境&lt;/p&gt; &lt;p&gt;上面这个注解一会我们会用在不同环境的配置类上面&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;下面来3个配置类 让3个配置类分别在不同环境中生效，会在这些配置类上面使用上面自定义的@EnvConditional注解来做条件限定。&lt;/p&gt; &lt;p&gt;每个配置类中通过@Bean来定义一个名称为name的bean，一会通过输出这个bean来判断哪个配置类生效了。&lt;/p&gt; &lt;p&gt;下面来看3个配置类的代码&lt;/p&gt; &lt;p&gt;测试环境配置类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package com.javacode2018.lesson001.demo25.test2;  import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration @EnvConditional(EnvConditional.Env.TEST)//@1 public class TestBeanConfig {     @Bean     public String name() {         return &amp;quot;我是测试环境!&amp;quot;;     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1指定的测试环境&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;开发环境配置类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package com.javacode2018.lesson001.demo25.test2;  import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration @EnvConditional(EnvConditional.Env.DEV) //@1 public class DevBeanConfig {     @Bean     public String name() {         return &amp;quot;我是开发环境!&amp;quot;;     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：指定的开发环境&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;生产环境配置类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package com.javacode2018.lesson001.demo25.test2;  import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  @Configuration @EnvConditional(EnvConditional.Env.PROD) //@1 public class ProdBeanConfig {     @Bean     public String name() {         return &amp;quot;我是生产环境!&amp;quot;;     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：指定的生产环境&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;下面来看一下条件类：EnvCondition&lt;/p&gt; &lt;p&gt;条件类会解析配置类上面@EnvConditional注解，得到环境信息。&lt;/p&gt; &lt;p&gt;然后和目前的环境对比，决定返回true还是false，如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata;   public class EnvCondition implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         //当前需要使用的环境         EnvConditional.Env curEnv = EnvConditional.Env.DEV; //@1         //获取使用条件的类上的EnvCondition注解中对应的环境         EnvConditional.Env env = (EnvConditional.Env) metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get(&amp;quot;value&amp;quot;).get(0);         return env.equals(curEnv);     }   } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：这个用来指定当前使用的环境，此处假定当前使用的是开发环境，这个我们以后可以任意发挥，比如将这些放到配置文件中，此处方便演示效果。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;Condition指定优先级&lt;/h2&gt; &lt;p&gt;多个Condition按顺序执行 @Condtional中value指定多个Condtion的时候，默认情况下会按顺序执行，还是通过代码来看一下效果。&lt;/p&gt; &lt;p&gt;下面代码中定义了3个Condition，每个Condition的matches方法中会输出当前类名，然后在配置类上面同时使用这3个Condition&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.type.AnnotatedTypeMetadata;   class Condition1 implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     } }   class Condition2 implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     } }   class Condition3 implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     } }   @Configuration @Conditional({Condition1.class, Condition2.class, Condition3.class}) public class MainConfig5 { } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;指定Condition的顺序 自定义的Condition可以实现PriorityOrdered接口或者继承Ordered接口，或者使用@Order注解，通过这些来指定这些Condition的优先级。&lt;/p&gt; &lt;p&gt;排序规则：先按PriorityOrdered排序，然后按照order的值进行排序；也就是：PriorityOrdered asc,order值 asc&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;下面这几个都可以指定order的值 接口：org.springframework.core.Ordered，有个getOrder方法用来返回int类型的值 接口：org.springframework.core.PriorityOrdered，继承了Ordered接口，所以也有getOrder方法 注解：org.springframework.core.annotation.Order，有个int类型的value参数指定Order的大小&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata;   @Order(1) //@1 class Condition1 implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     } }   class Condition2 implements Condition, Ordered { //@2     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     }       @Override     public int getOrder() { //@3         return 0;     } }   class Condition3 implements Condition, PriorityOrdered { //@4     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         System.out.println(this.getClass().getName());         return true;     }       @Override     public int getOrder() {         return 1000;     } }   @Configuration @Conditional({Condition1.class, Condition2.class, Condition3.class})//@5 public class MainConfig6 { } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：Condition1通过@Order指定顺序，值为1&lt;/p&gt; &lt;p&gt;@2：Condition2通过实现了Ordered接口来指定顺序，@3：getOrder方法返回1&lt;/p&gt; &lt;p&gt;@4：Condition3实现了PriorityOrdered接口，实现这个接口需要重写getOrder方法，返回1000&lt;/p&gt; &lt;p&gt;@5：Condtion顺序为1、2、3&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;ConfigurationCondition使用&lt;/h2&gt; &lt;p&gt;ConfigurationCondition使用的比较少，很多地方对这个基本上也不会去介绍，Condition接口基本上可以满足99%的需求了，但是springboot中却大量用到了ConfigurationCondition这个接口。&lt;/p&gt; &lt;p&gt;ConfigurationCondition通过解释比较难理解，来个案例感受一下：&lt;/p&gt; &lt;p&gt;来一个普通的类：Service&lt;/p&gt; &lt;pre&gt;&lt;code&gt; public class Service { } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;来一个配置类，通过配置类注册上面这个Service&lt;/p&gt; &lt;pre&gt;&lt;code class="language-kotlin"&gt;   import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   @Configuration public class BeanConfig1 {     @Bean     public Service service() {         return new Service();     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再来一个配置类：BeanConfig2&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   @Configuration public class BeanConfig2 {     @Bean     public String name() {         return &amp;quot;路人甲Java&amp;quot;;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;来一个总的配置类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import;   @Configuration @Import({BeanConfig1.class, BeanConfig2.class}) public class MainConfig7 { } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在我们有个需求&lt;/p&gt; &lt;p&gt;当容器中有Service这种类型的bean的时候，BeanConfig2才生效。&lt;/p&gt; &lt;p&gt;很简单吧，加个Condition就行了，内部判断容器中是否有Service类型的bean，继续&lt;/p&gt; &lt;p&gt;来个自定义的Condition&lt;/p&gt; &lt;pre&gt;&lt;code class="language-kotlin"&gt;     import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata;   public class MyCondition1 implements Condition {     @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         //获取spring容器         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();         //判断容器中是否存在Service类型的bean         boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();         return existsService;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;上面代码很简单，判断容器中是否有IService类型的bean。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Configuration @Conditional(MyCondition1.class) public class BeanConfig2 {     @Bean     public String name() {         return &amp;quot;路人甲Java&amp;quot;;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;结果name永远注册不上&lt;/p&gt; &lt;p&gt;为什么？ 在文章前面我们说过，配置类的处理会依次经过2个阶段：配置类解析阶段和bean注册阶段，Condition接口类型的条件会对这两个阶段都有效，解析阶段的时候，容器中是还没有Service这个bean的，配置类中通过@Bean注解定义的bean在bean注册阶段才会被注册到spring容器，所以BeanConfig2在解析阶段去容器中是看不到Service这个bean的，所以就被拒绝了。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;此时我们需要用到ConfigurationCondition了，让条件判断在bean注册阶段才起效。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;自定义一个ConfigurationCondition类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;   import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.core.type.AnnotatedTypeMetadata;   public class MyConfigurationCondition1 implements ConfigurationCondition {     @Override     public ConfigurationPhase getConfigurationPhase() {         return ConfigurationPhase.REGISTER_BEAN; //@1     }       @Override     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         //获取spring容器         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();         //判断容器中是否存在Service类型的bean         boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();         return existsService;     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;@1：指定条件在bean注册阶段，这个条件才有效&lt;/p&gt; &lt;p&gt;matches方法中的内容直接复制过来，判断规则不变&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;修改BeanConfig2的类容&lt;/p&gt; &lt;pre&gt;&lt;code class="language-less"&gt;将 @Conditional(MyCondition1.class) 替换为 @Conditional(MyConfigurationCondition1.class) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时name这个bean被注入了。&lt;/p&gt; &lt;p&gt;可以再试试将BeanConfig1中service方法上面的@Bean去掉，此时Service就不会被注册到容器&lt;/p&gt; &lt;p&gt;判断bean存不存在的问题，通常会使用ConfigurationCondition这个接口，阶段为：REGISTER_BEAN，这样可以确保条件判断是在bean注册阶段执行的。&lt;/p&gt; &lt;p&gt;对springboot比较熟悉的，它里面有很多@Conditionxxx这样的注解，可以去看一下这些注解，很多都实现了ConfigurationCondition接口。&lt;/p&gt; &lt;p&gt;Spring中这块的源码 @Conditional注解是被下面这个类处理的&lt;/p&gt; &lt;pre&gt;&lt;code class="language-crystal"&gt;org.springframework.context.annotation.ConfigurationClassPostProcessor &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;@Conditional注解可以标注在spring需要处理的对象上（配置类、@Bean方法），相当于加了个条件判断，通过判断的结果，让spring觉得是否要继续处理被这个注解标注的对象&lt;/p&gt; &lt;p&gt;spring处理配置类大致有2个过程：解析配置类、注册bean，这两个过程中都可以使用@Conditional来进行控制spring是否需要处理这个过程&lt;/p&gt; &lt;p&gt;Condition默认会对2个过程都有效&lt;/p&gt; &lt;p&gt;ConfigurationCondition控制得更细一些，可以控制到具体那个阶段使用条件判断&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 23 Dec 2022 16:06:00 GMT</pubDate>
    </item>
    <item>
      <title>SpringDataElasticsearch控制台打印查询语句</title>
      <link>https://maruifu.cn/article/265</link>
      <content:encoded>&lt;h2&gt;application.yml增加配置&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 日志配置 logging:   level:      #es日志     org.springframework.data.elasticsearch.client.WIRE : trace  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;增加配置类&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;package com.maruifu.business.config;  import org.elasticsearch.client.RestHighLevelClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.RestClients;  /**  * 用于打印dsl语句  */ @Configuration public class EsConfig {           @Bean(destroyMethod = &amp;quot;close&amp;quot;)     public RestHighLevelClient restClient() {          ClientConfiguration clientConfiguration = ClientConfiguration.builder()                 .connectedTo(&amp;quot;172.16.45.138:11700&amp;quot;)                 .build();         RestHighLevelClient client = RestClients.create(clientConfiguration).rest();         return client;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看打印效果&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/12/15/image-20221215224015543.png" alt="image-20221215224015543" title="image-20221215224015543" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 15 Dec 2022 14:40:59 GMT</pubDate>
    </item>
    <item>
      <title>navicat16 mac版无限重置试用期脚本</title>
      <link>https://maruifu.cn/article/264</link>
      <content:encoded>&lt;h1&gt;免责声明&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;本脚本为免费使用，本脚本只供个人学习使用，使用需严格遵守开源许可协议。严禁用于商业用途，禁止进行任何盈利活动。对一切非法使用所产生的后果，概不负责！&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;  #!/bin/bash  set -e  file=$(defaults read /Applications/Navicat\ Premium.app/Contents/Info.plist)  regex=&amp;quot;CFBundleShortVersionString = \&amp;quot;([^\.]+)&amp;quot; [[ $file =~ $regex ]]  version=${BASH_REMATCH[1]}  echo &amp;quot;Detected Navicat Premium version $version&amp;quot;  case $version in     &amp;quot;16&amp;quot;)         file=~/Library/Preferences/com.navicat.NavicatPremium.plist         ;;     &amp;quot;15&amp;quot;)         file=~/Library/Preferences/com.prect.NavicatPremium15.plist         ;;     *)         echo &amp;quot;Version '$version' not handled&amp;quot;         exit 1        ;; esac  echo -n &amp;quot;Reseting trial time...&amp;quot;  regex=&amp;quot;([0-9A-Z]{32}) = &amp;quot; [[ $(defaults read $file) =~ $regex ]]  hash=${BASH_REMATCH[1]}  if [ ! -z $hash ]; then     defaults delete $file $hash fi  regex=&amp;quot;\.([0-9A-Z]{32})&amp;quot; [[ $(ls -a ~/Library/Application\ Support/PremiumSoft\ CyberTech/Navicat\ CC/Navicat\ Premium/ | grep '^\.') =~ $regex ]]  hash2=${BASH_REMATCH[1]}  if [ ! -z $hash2 ]; then     rm ~/Library/Application\ Support/PremiumSoft\ CyberTech/Navicat\ CC/Navicat\ Premium/.$hash2 fi  echo &amp;quot; Done&amp;quot;  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 07 Dec 2022 13:48:00 GMT</pubDate>
    </item>
    <item>
      <title>JDK1.8 HashMap数据结构</title>
      <link>https://maruifu.cn/article/263</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h2&gt;HashMap集合简介&lt;/h2&gt; &lt;p&gt;HashMap基于哈希表的Map接口实现，是以&lt;strong&gt;key-value&lt;/strong&gt;存储形式存在，即主要用来存放键值对。HashMap的实现不是同步的，这意味着它不是线程安全的。它的key、value都可以为null。此外，HashMap中的映射不是有序的。&lt;/p&gt; &lt;p&gt;JDK1.8之前的HashMap由数组+链表组成的，数组是HashMap的主体，链表则是主要为了节解决&lt;strong&gt;哈希碰撞&lt;/strong&gt;(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)而存在的（“拉链法”解决冲突）。&lt;/p&gt; &lt;p&gt;JDK1.8之后在解决哈希冲突时有了较大的变化，当&lt;strong&gt;链表长度大于阈值&lt;/strong&gt;（或者红黑树的边界值，默认为8）并且当前&lt;strong&gt;数组的长度大于64&lt;/strong&gt;时，此时此索引位置上的所有数据改为使用红黑树存储。&lt;/p&gt; &lt;p&gt;数组里面都是key-value的实例，在JDK1.8之前叫做Entry，在JDK1.8之后叫做Node。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129230734919.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;由于它的key、value都为null，所以在插入的时候会根据key的hash去计算一个index索引的值。计算索引的方法如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**  * 根据key求index的过程  * 1,先用key求出hash值  */ static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h &amp;gt;&amp;gt;&amp;gt; 16); } //2,再用公式index = (n - 1) &amp;amp; hash（n是数组长度） int hash=hash(key); index=(n-1)&amp;amp;hash; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里的Hash算法本质上就是三步：取key的hashCode值、高位运算、取模运算。&lt;/p&gt; &lt;p&gt;这样的话比如说put(&amp;quot;A&amp;quot;,王炸)，插入了key为&amp;quot;A&amp;quot;的元素，这时候通过上述公式计算出插入的位置index，若index为3则结果如下（即hash(&amp;quot;A&amp;quot;)=3）：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129230824746.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;那么，HashMap中的链表又是干什么用的呢？&lt;/p&gt; &lt;p&gt;大家都知道数组的长度是有限的，&lt;strong&gt;在有限的长度里面使用哈希函数计算index的值时，很有可能插入的k值不同，但所产生的hash是相同的&lt;/strong&gt;（也叫做哈希碰撞），这也就是哈希函数存在一定的概率性。就像上面的K值为A的元素，如果再次插入一个K值为a的元素，很有可能所产生的index值也为3，也就是即hash(&amp;quot;a&amp;quot;)=3；那这就形成了链表，这种解决哈希碰撞的方法也叫做拉链法。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129230843137.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;当这个链表长度大于阈值8并且数组长度大于64则进行将链表变为红黑树。&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;将链表转换成红黑树前会判断，如果阈值大于8，但是数组长度小64，此时并不会将链表变为红黑树。而是选择进行数组扩容。&lt;/p&gt; &lt;p&gt;这样做的目的是因为数组比较小，尽量避开红黑树结构，这种情况下变为红黑树结构，反而会降低效率，因为红黑树需要进行左旋，右旋，变色这些操作来保持平衡。同事数组长度小于64时，搜索时间相对快一些。所以综上所述为了提高性能和减少搜索时间，底层在阈值大于8并且数组长度大于64时，链表才转换为红黑树。具体可以参考treeifyBin方法。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;当然虽然增了红黑树作为底层数据结构，结构变得复杂了，但是阈值大于8并且数组长度大于64时，链表转换为红黑树时，效率也变得更高效。&lt;/p&gt; &lt;h3&gt;特点&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;存取无序的&lt;/li&gt; &lt;li&gt;键和值位置都可以是null，但是键位置只能是一个null&lt;/li&gt; &lt;li&gt;键位置是唯一的，底层的数据结构控制键的&lt;/li&gt; &lt;li&gt;jdk1.8前数据结构是：链表 + 数组  jdk1.8之后是 ：链表 + 数组  + 红黑树&lt;/li&gt; &lt;li&gt;阈值(边界值) &amp;gt; 8 并且数组长度大于64，才将链表转换为红黑树，变为红黑树的目的是为了高效的查询。&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;HsahMap底层数据结构&lt;/h2&gt; &lt;h3&gt;HashMap存储数据的过程&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129231153135.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;每一个Node结点都包含&lt;strong&gt;键值对的key，value&lt;/strong&gt;还有计算出来的&lt;strong&gt;hash值&lt;/strong&gt;，还保存着下一个 &lt;strong&gt;Node 的引用 next&lt;/strong&gt;（如果没有下一个 Node，next = null），来看看Node的源码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;static class Node&amp;lt;K,V&amp;gt; implements Map.Entry&amp;lt;K,V&amp;gt; {         final int hash;         final K key;         V value;         Node&amp;lt;K,V&amp;gt; next;         ...    } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;HashMap存储数据需要用到put()方法，关于这些方法的详解，我们下节再说，这里简要说一下；&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public static void main(String[] args) {         HashMap&amp;lt;String,Integer&amp;gt; hmap=new HashMap&amp;lt;&amp;gt;();         hmap.put(&amp;quot;斑&amp;quot;,55);         hmap.put(&amp;quot;镜&amp;quot;,63);         hmap.put(&amp;quot;带土&amp;quot;,25);         hmap.put(&amp;quot;鼬&amp;quot;,9);         hmap.put(&amp;quot;佐助&amp;quot;,43);         hmap.put(&amp;quot;斑&amp;quot;,88);         System.out.println(hmap);     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当创建HashMap集合对象的时候，在jdk1.8之前，构造方法中会创建很多&lt;strong&gt;长度是16的Entry[] table&lt;/strong&gt;用来存储键值对数据的。在jdk1.8之后不是在HashMap的构造方法底层创建数组了，是在&lt;strong&gt;第一次调用put方法&lt;/strong&gt;时创建的数组，&lt;strong&gt;Node[] table&lt;/strong&gt;用来存储键值对数据的。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;比方说我们向哈希表中存储&amp;quot;斑&amp;quot;-55的数据，根据K值(&amp;quot;斑&amp;quot;)调用String类中重写之后的hashCode()方法计算出值（数量级很大），然后结合数组长度采用取余（(n-1)&amp;amp;hash）操作或者其他操作方法来计算出向Node数组中存储数据的空间的索引值。如果计算出来的索引空间没有数据，则直接将&amp;quot;斑&amp;quot;-55数据存储到数组中。跟上面的&amp;quot;A-王炸&amp;quot;数据差不多。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;我们回到上方的数组图，如果此时再插入&amp;quot;A-蘑菇&amp;quot;元素，那么首先根据Key值(&amp;quot;A&amp;quot;)调用hashCode()方法结合数组长度计算出索引肯定也是3，此时比较后存储的&amp;quot;A-蘑菇&amp;quot;和已经存在的数据&amp;quot;A-王炸&amp;quot;的hash值是否相等，如果hash相等，此时发生hash碰撞。&lt;/p&gt; &lt;p&gt;那么底层会调用&amp;quot;A&amp;quot;所属&lt;strong&gt;类String中的equals方法&lt;/strong&gt;比较两个key内容是否相等，若相等，则后添加的数据直接覆盖已经存在的Value，也就是&amp;quot;蘑菇&amp;quot;直接覆盖&amp;quot;王炸&amp;quot;；若不相等，继续向下和其他数据的key进行比较，如果都不相等，则规划出一个节点存储数据。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129231359917.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h3&gt;哈希碰撞相关的问题&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;哈希表底层采用何种算法计算hash值？还有哪些算法可以计算出hash值？&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;底层是采用key的hashCode方法的值结合数组长度进行&lt;strong&gt;无符号右移（&amp;gt;&amp;gt;&amp;gt;）&lt;/strong&gt;、按位异或（^）、按位与（&amp;amp;）计算出索引的&lt;/p&gt; &lt;p&gt;还可以采用：平方取中法，取余数，伪随机数法。这三种效率都比较低。而无符号右移16位异或运算效率是最高的。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;当两个对象的hashCode相等时会怎么样？&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;会产生哈希碰撞，若&lt;strong&gt;key值内容相同则替换旧的value&lt;/strong&gt;.否则连接到链表后面，&lt;strong&gt;链表长度超过阈值8&lt;/strong&gt;就转换为红黑树存储。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞？&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;只要&lt;strong&gt;两个元素的key计算的哈希值相同&lt;/strong&gt;就会发生哈希碰撞。jdk8前使用链表解决哈希碰撞。jdk8之后使用链表+红黑树解决哈希碰撞。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;如果两个键的hashcode相同，如何存储键值对？&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;hashcode相同，通过equals比较内容是否相同。&lt;strong&gt;相同：则新的value覆盖之前的value 不相同：则将新的键值对添加到哈希表中&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;红黑树结构&lt;/h3&gt; &lt;p&gt;当位于一个链表中的元素较多，即hash值相等但是内容不相等的元素较多时，通过key值依次查找的效率较低。而jdk1.8中，哈希表存储采用数组+链表+红黑树实现，当链表长度(阀值)超过 8 时且当前数组的长度 &amp;gt; 64时，将链表转换为红黑树，这样大大减少了查找时间。jdk8在哈希表中&lt;strong&gt;引入红黑树的原因只是为了查找效率更高。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129231455261.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;JDK 1.8 以前 HashMap 的实现是 数组+链表，即使哈希函数取得再好，也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时，这个桶下有一条长长的链表，这个时候 HashMap 就相当于一个单链表，假如单链表有 n 个元素，遍历的时间复杂度就是 O(n)，完全失去了它的优势。针对这种情况，JDK 1.8 中引入了 红黑树（&lt;strong&gt;查找时间复杂度为 O(logn)&lt;/strong&gt;）来优化这个问题。当链表长度很小的时候，即使遍历，速度也非常快，但是当链表长度不断变长，肯定会对查询性能有一定的影响，所以才需要转成树。&lt;/p&gt; &lt;h3&gt;存储流程图&lt;/h3&gt; &lt;p&gt;HashMap存放数据是用的put方法，put 方法内部调用的是 putVal() 方法，所以对 put 方法的分析也是对 putVal 方法的分析，整个过程比较复杂，流程图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129231515075.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;来看看put()源码：&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public V put(K key, V value) {     //对key的hashCode()做hash，调用的是putVal方法         return putVal(hash(key), key, value, false, true);     }      final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                    boolean evict) {         Node&amp;lt;K,V&amp;gt;[] tab; Node&amp;lt;K,V&amp;gt; p; int n, i;         /*            1，tab为空则开始创建，            2，(tab = table) == null 表示将空的table赋值给tab,然后判断tab是否等于null，第一次肯定是null            3，(n = tab.length) == 0 表示没有为table分配内存            4，tab为空，执行代码 n = (tab = resize()).length; 进行扩容。并将初始化好的数组长度赋值给n.            5，执行完n = (tab = resize()).length，数组tab每个空间都是null         */                 if ((tab = table) == null || (n = tab.length) == 0)             //调用resize()方法进行扩容             n = (tab = resize()).length;          /*         1，i = (n - 1) &amp;amp; hash 表示计算数组的索引赋值给i，即确定元素存放在哪个桶中         2，p = tab[i = (n - 1) &amp;amp; hash]表示获取计算出的位置的数据赋值给节点p         3，(p = tab[i = (n - 1) &amp;amp; hash]) == null 判断节点位置是否等于null，          如果为null，则执行代码：tab[i] = newNode(hash, key, value, null);根据键值对创建新的节点放入该位置的桶中         小结：如果当前桶没有哈希碰撞冲突，则直接把键值对插入空间位置     */          if ((p = tab[i = (n - 1) &amp;amp; hash]) == null)             //节点位置为null，则直接进行插入操作             tab[i] = newNode(hash, key, value, null);         //节点位置不为null，表示这个位置已经有值了，于是需要进行比较hash值是否相等         else {             Node&amp;lt;K,V&amp;gt; e; K k;              /*           比较桶中第一个元素(数组中的结点)的hash值和key是否相等                1，p.hash == hash 中的p.hash表示原来存在数据的hash值  hash表示后添加数据的hash值 比较两个hash值是否相等                2，(k = p.key) == key ：p.key获取原来数据的key赋值给k  key表示后添加数据的key 比较两个key的地址值是否相等                3，key != null &amp;amp;&amp;amp; key.equals(k)：能够执行到这里说明两个key的地址值不相等，那么先判断后添加的key是否等于null，如果不等于null再调用equals方法判断两个key的内容是否相等         */             if (p.hash == hash &amp;amp;&amp;amp;                 ((k = p.key) == key || (key != null &amp;amp;&amp;amp; key.equals(k))))                  /*                  说明：两个元素哈希值相等（哈希碰撞），并且key的值也相等                  将旧的元素整体对象赋值给e，用e来记录                 */                  e = p;             // hash值不相等或者key不相等；判断p是否为红黑树结点             else if (p instanceof TreeNode)                 // 是红黑树，调用树的插入方法                 e = ((TreeNode&amp;lt;K,V&amp;gt;)p).putTreeVal(this, tab, hash, key, value);             // 说明是链表节点，这时进行插入操作             else {                 /*                 1，如果是链表的话需要遍历到最后节点然后插入                 2，采用循环遍历的方式，判断链表中是否有重复的key                 */                 for (int binCount = 0; ; ++binCount) {                     /*                  1)e = p.next 获取p的下一个元素赋值给e                  2)(e = p.next) == null 判断p.next是否等于null，等于null，说明p没有下一个元     素，那么此时到达了链表的尾部，还没有找到重复的key,则说明HashMap没有包含该键                  将该键值对插入链表中                 */                     if ((e = p.next) == null) {                         p.next = newNode(hash, key, value, null);                         //插入后发现链表长度大于8，转换成红黑树结构                         if (binCount &amp;gt;= TREEIFY_THRESHOLD - 1)                              //转换为红黑树                             treeifyBin(tab, hash);                         break;                     }                     //key值以及存在直接覆盖value                     if (e.hash == hash &amp;amp;&amp;amp;                         ((k = e.key) == key || (key != null &amp;amp;&amp;amp; key.equals(k))))                         break;                     p = e;                 }             }             //若结点为null，则不进行插入操作             if (e != null) {                  V oldValue = e.value;                 if (!onlyIfAbsent || oldValue == null)                     e.value = value;                 afterNodeAccess(e);                 return oldValue;             }         }         //修改记录次数         ++modCount;         // 判断实际大小是否大于threshold阈值，如果超过则扩容         if (++size &amp;gt; threshold)             resize();         // 插入后回调         afterNodeInsertion(evict);         return null;     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;小结：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;根据哈希表中元素个数确定是&lt;strong&gt;扩容还是树形化&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;如果是树形化遍历桶中的元素，创建相同个数的树形节点，复制内容，建立起联系&lt;/li&gt; &lt;li&gt;然后让桶中的第一个元素指向新创建的树根节点，替换桶的链表内容为树形化内容&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;HashMap的扩容机制&lt;/h2&gt; &lt;p&gt;我们知道，数组的容量是有限的，多次插入数据的话，到达一定数量就会进行扩容；先来看两个问题&lt;/p&gt; &lt;h3&gt;什么时候需要扩容？&lt;/h3&gt; &lt;p&gt;当HashMap中的&lt;strong&gt;元素个数超过数组长度loadFactor(负载因子)&lt;strong&gt;时，就会进行数组扩容，loadFactor的默认值是0.75,这是一个折中的取值。也就是说，默认情况下，数组大小为16，那么当HashMap中的元素个数超过16×0.75=12(这个值就是阈值)的时候，就把数组的大小扩展为2×16=32，即扩大一倍，然后&lt;/strong&gt;重新计算每个元素在数组中的位置&lt;/strong&gt;，而这是一个非常耗性能的操作，所以如果我们已经预知HashMap中元素的个数，那么预知元素的个数能够有效的提高HashMap的性能。&lt;/p&gt; &lt;h3&gt;怎么进行扩容的？&lt;/h3&gt; &lt;p&gt;HashMap在进行扩容时使用 resize() 方法，计算 table 数组的新容量和 Node 在新数组中的新位置，将旧数组中的值复制到新数组中，从而实现自动扩容。因为每次扩容都是翻倍，与原来计算的 (n-1)&amp;amp;hash的结果相比，只是多了一个bit位，所以节点要么就在原来的位置，要么就被分配到&amp;quot;&lt;strong&gt;原位置+旧容量&lt;/strong&gt;&amp;quot;这个位置。&lt;/p&gt; &lt;p&gt;因此，我们在扩充HashMap的时候，不需要重新计算hash，只需要看看原来hash值新增的那个bit是1还是0就可以了，是0的话索引没变，是1的话索引变成“原索引+oldCap(&lt;strong&gt;原位置+旧容量&lt;/strong&gt;)”。这里不再详细赘述，可以看看下图为16扩充为32的resize示意图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/29/640-20221129231614682.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;HashMap数组长度为什么是2的次幂&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;我们先看看它的成员变量：&lt;/p&gt; &lt;h3&gt;序列化版本号&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;private static final long serialVersionUID = 362498820763181265L; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;集合的初始化容量initCapacity&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;//默认的初始容量是16 -- 1&amp;lt;&amp;lt;4相当于1*2的4次方---1*16 static final int DEFAULT_INITIAL_CAPACITY = 1 &amp;lt;&amp;lt; 4;    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;初始化容量默认是16，容量过大，遍历时会减慢速度，效率低；容量过小，那么扩容的次数变多，非常耗费性能。&lt;/p&gt; &lt;h3&gt;负载因子&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;/**      * The load factor used when none specified in constructor.      */     static final float DEFAULT_LOAD_FACTOR = 0.75f; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;初始默认值为0.75，若过大，会导致哈希冲突的可能性更大；若过小，扩容的次数也会提高。&lt;/p&gt; &lt;h3&gt;为什么必须是2的n次幂？&lt;/h3&gt; &lt;p&gt;当向HashMap中添加一个元素的时候，需要根据key的hash值，去确定其在数组中的具体位置。HashMap为了提高存取效率，要尽量较少碰撞，就是要尽量把数据分配均匀，每个链表长度大致相同，这个实现就在把数据存到哪个链表中的算法。&lt;/p&gt; &lt;p&gt;这个算法实际就是取模，hash%length，计算机中直接求余效率不如位移运算。所以源码中做了优化,使用 &lt;code&gt;hash&amp;amp;(length-1)&lt;/code&gt;，而实际上&lt;code&gt;hash%length&lt;/code&gt;等于&lt;code&gt;hash&amp;amp;(length-1)&lt;/code&gt;的前提是length是2的n次幂。&lt;/p&gt; &lt;h3&gt;如果输入值不是2的幂会怎么样？&lt;/h3&gt; &lt;p&gt;如果数组长度不是2的n次幂，计算出的索引特别容易相同，及其容易发生hash碰撞，导致其余数组空间很大程度上并没有存储数据，链表或者红黑树过长，效率降低。&lt;/p&gt; &lt;p&gt;1，当根据key的hash确定其在数组的位置时，如果n为2的幂次方**，**&lt;strong&gt;可以保证数据的均匀插入&lt;/strong&gt;，如果n不是2的幂次方，可能数组的一些位置永远不会插入数据，&lt;strong&gt;浪费数组的空间，加大hash冲突。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;2，一般可能会想通过 % 求余来确定位置，这样也可以，只不过性能不如 &amp;amp; 运算。而且当n是2的幂次方时：hash &amp;amp; (length - 1) == hash % length&lt;/p&gt; &lt;p&gt;3，因此，HashMap 容量为2次幂的原因，就是**为了数据的的均匀分布，减少hash冲突，**毕竟hash冲突越大，代表数组中一个链的长度越大，这样的话会降低hashmap的性能&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 29 Nov 2022 15:21:00 GMT</pubDate>
    </item>
    <item>
      <title>maven子pom没有继承到父pom依赖版本</title>
      <link>https://maruifu.cn/article/262</link>
      <content:encoded>&lt;p&gt;Java项目很多都会有子module，一般父项目没有逻辑代码，在父项目pom.xml中注明依赖、version和其他一些公用的东西，子module的pom继承父pom，子pom就不用写依赖的版本了，但至少也要写用到依赖的groupId、artifactId，这样默认会使用父项目依赖的版本。子pom也可以写版本，这样就不受父pom影响了，和继承类似，但还是有些区别。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;使用时需要注意，我就是忘了其中一项，没有生效：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;父pom需要添加&lt;code&gt;&amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;&lt;/code&gt;&lt;/li&gt; &lt;li&gt;父pom需要用&lt;code&gt;&amp;lt;modules&amp;gt;&amp;lt;module&amp;gt;子module名&amp;lt;/module&amp;gt;&amp;lt;/modules&amp;gt;&lt;/code&gt;注明子module有哪些&lt;/li&gt; &lt;li&gt;父pom声明依赖时&lt;code&gt;&amp;lt;dependencies&amp;gt;&lt;/code&gt;外要嵌套&lt;code&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/code&gt;才能被子pom继承到，我就是忘了这点&lt;/li&gt; &lt;li&gt;子pom需要通过&lt;code&gt;&amp;lt;parent&amp;gt;&amp;lt;/parent&amp;gt;&lt;/code&gt;指定父项目，声明依赖时就默认会用父pom中的版本了&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;dependencyManagement：像上面提到的，一般在父项目中会声明这个元素，和普通依赖用法类似，这个元素并不会真的引入依赖，只会标明依赖和版本，子项目会从父项目找&lt;code&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/code&gt;从而确定需要引用依赖版本，类似于模板模式。&lt;/p&gt; &lt;p&gt;父pom例子：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;  &amp;lt;!-- 省略无关配置 --&amp;gt;   &amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;      &amp;lt;modules&amp;gt;         &amp;lt;!-- 注明所有子module --&amp;gt;         &amp;lt;module&amp;gt;module1&amp;lt;/module&amp;gt;         &amp;lt;module&amp;gt;module2&amp;lt;/module&amp;gt;     &amp;lt;/modules&amp;gt;      &amp;lt;dependencyManagement&amp;gt;         &amp;lt;dependencies&amp;gt;             &amp;lt;!-- 以Lombk为例 --&amp;gt;             &amp;lt;dependency&amp;gt;                 &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;1.18.8&amp;lt;/version&amp;gt;             &amp;lt;/dependency&amp;gt;         &amp;lt;/dependencies&amp;gt;     &amp;lt;/dependencyManagement&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;子pom module1例子：&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;    &amp;lt;!-- 省略无关配置 --&amp;gt;     &amp;lt;parent&amp;gt;         &amp;lt;groupId&amp;gt;父项目group&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;父项目artifactId&amp;lt;/artifactId&amp;gt;         &amp;lt;version&amp;gt;父项目version&amp;lt;/version&amp;gt;        &amp;lt;relativePath&amp;gt;../pom.xml&amp;lt;/relativePath&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;!--这样会使用父pom中的依赖版本1.18.8，如果这里写version就不会使用父pom里的版本了--&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;             &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;relativePath&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;指定查找该父项目pom.xml的(相对)路径。默认顺序：relativePath &amp;gt; 本地仓库 &amp;gt; 远程仓库&lt;/li&gt; &lt;li&gt;没有relativePath标签等同…/pom.xml, 即默认从当前pom文件的上一级目录找&lt;/li&gt; &lt;li&gt;表示不从relativePath找, 直接从本地仓库找,找不到再从远程仓库找&lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;经过 maven3.6版本测试，似乎没有relativePath标签时，它没有从当前pom文件的上一级目录找，子模块继承不到父模块中dependencyManagement中包的version信息。子模块想要用父模块pom中的版本，请注意配置relativePath属性！&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Tue, 29 Nov 2022 14:45:00 GMT</pubDate>
    </item>
    <item>
      <title>Druid Maven引入缺少Jconsole.jar和Tools.jar的问题原因及解决办法</title>
      <link>https://maruifu.cn/article/260</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt; Could not resolve dependencies for project com.xxx:xxx-framework🏺3.8.4: The following artifacts could not be resolved: com.sun:tools🏺1.8, com.sun:jconsole🏺1.8: Could not find artifact com.sun:tools🏺1.8 at specified path /root/.m2/repository/com/alibaba/druid/1.2.11/lib/openjdk-1.8-tools.jar  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;问题原因&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;OracleJDK面临商业闭源风险，所以用到的Jconsole和Tools引入的是OpenJDK&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;openjdk-1.8-jconsole.jar openjdk-1.8-tools.jar &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;h3&gt;使用OpenJDK&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;com.sun&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;tools&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.8&amp;lt;/version&amp;gt;             &amp;lt;scope&amp;gt;system&amp;lt;/scope&amp;gt;             &amp;lt;systemPath&amp;gt;${JAVA_HOME}/lib/tools.jar&amp;lt;/systemPath&amp;gt; &amp;lt;/dependency&amp;gt;  &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;jdk.tools&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;jdk.tools&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.8&amp;lt;/version&amp;gt;             &amp;lt;scope&amp;gt;system&amp;lt;/scope&amp;gt;             &amp;lt;systemPath&amp;gt;${JAVA_HOME}/lib/tools.jar&amp;lt;/systemPath&amp;gt; &amp;lt;/dependency&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;排除使用&lt;/h3&gt; &lt;p&gt;大多数未用到这个功能（未参与源码(开发），则排除即可&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;exclusions&amp;gt;     &amp;lt;exclusion&amp;gt;         &amp;lt;groupId&amp;gt;com.sun&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;jconsole&amp;lt;/artifactId&amp;gt;     &amp;lt;/exclusion&amp;gt;     &amp;lt;exclusion&amp;gt;         &amp;lt;groupId&amp;gt;com.sun&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;tools&amp;lt;/artifactId&amp;gt;     &amp;lt;/exclusion&amp;gt; &amp;lt;/exclusions&amp;gt;  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 22 Nov 2022 15:05:00 GMT</pubDate>
    </item>
    <item>
      <title>Jenkins+Maven+Github+Springboot实现可持续自动部署(非常详细)</title>
      <link>https://maruifu.cn/article/259</link>
      <content:encoded>&lt;h2&gt;一、安装内容&lt;/h2&gt; &lt;p&gt;　　Jenkins（&lt;strong&gt;本文主要安装&lt;/strong&gt;）、Maven、Git、JDK（&lt;strong&gt;这个三个安装过程略&lt;/strong&gt;）&lt;/p&gt; &lt;p&gt;　　Jenkins与Github配合实现持续集成需要&lt;strong&gt;注意&lt;/strong&gt;以下几点：&lt;/p&gt; &lt;p&gt;　　　　①Jenkins要部署到&lt;strong&gt;外网&lt;/strong&gt;，因为内网Github是无法访问到的（&lt;strong&gt;走过的坑！&lt;/strong&gt;），这里我租用的是阿里云的服务器实现。&lt;/p&gt; &lt;p&gt;　　　　②Jenkins所在的主机需要安装Git,通过Git程序从Github上clone代码&lt;/p&gt; &lt;p&gt;　　　　③在Jenkins中需要指定Git、Maven、JDK，路子都是相同的。&lt;/p&gt; &lt;p&gt;　　　　④在Github上使用每一个repository的webhook方式远程触发jenkins构建&lt;/p&gt; &lt;p&gt;　　　　⑤在Jenkins内关闭“防止跨站点请求伪造”&lt;/p&gt; &lt;h2&gt;二、实现过程&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930093417747-183564081.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;1.不使用Jenkins：&lt;/h3&gt; &lt;p&gt;　　①我们开发人员需要编写代码，提交到版本控制服务器（Svn、Git）&lt;/p&gt; &lt;p&gt;　　②我们开发人员需要手动拉取最新代码，构建maven工程，进行打包（war或jar）&lt;/p&gt; &lt;p&gt;　　③我们开发人员手动部署war或jar至应用服务器&lt;/p&gt; &lt;p&gt;　　④一旦项目上线，bug修改等小改动问题都要重复以上过程&lt;/p&gt; &lt;h3&gt;2.使用Jenkins：&lt;/h3&gt; &lt;p&gt;　　①同样，业务代码还是需要开发人员来编写并commit或push至版本控制自服务器（SVN、Git）&lt;/p&gt; &lt;p&gt;　　②通过以上图，我们发现，Jenkins帮我们做了：&lt;strong&gt;拉取最新代码、打包、部署&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;　　③我们开发人员只需专注于：业务代码的编写&lt;/p&gt; &lt;p&gt;　　④一旦项目上线，bug修改等小改动问题，我们开发人员只需提交最新代码即可，另一边的测试或最终用户就能看到最终效果，大大减少我们开发人员的工作量&lt;/p&gt; &lt;h2&gt;三、Github仓库准备&lt;/h2&gt; &lt;p&gt;　　鄙人写了一个小demo，用IDEA+Maven构建的SpringBoot的web项目：https://github.com/Simple-Coder/jenkins-demo&lt;/p&gt; &lt;h2&gt;四、Jenkins的下载安装&lt;/h2&gt; &lt;p&gt;　　下载地址：https://jenkins.io/download/（下载速度极慢），这里我已经将最新版的war包上传至CSDN下载，亲测可用，下载地址如下：&lt;/p&gt; &lt;p&gt;　　https://download.csdn.net/download/qq_39947137/11829473&lt;/p&gt; &lt;h3&gt;1.Jenkins的war包下载&lt;/h3&gt; &lt;p&gt;　　由于我是Java开发人员，最喜欢一键部署了，下载Jenkins的war直接丢进tomcat的webapps目录即可完成部署。&lt;/p&gt; &lt;p&gt;　　&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930102452634-960417096.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;然后找到documentation，get started，这是学习一门新技术的一些套路了：官网----&amp;gt;download----&amp;gt;documentation----&amp;gt;getting started&lt;/p&gt; &lt;h3&gt;2.将war包丢进服务器的tomcat/webapps目录，浏览器访问：IP:端口/jenkins&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930110145325-897740310.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;注：第一次安装，这里坑了我好久！！！（vim /root/.jenkins/hudson.model.UpdateCenter.xml）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;打开 hudson.model.UpdateCenter.xml&lt;/p&gt; &lt;p&gt;把 http://updates.jenkins-ci.org/update-center.json&lt;/p&gt; &lt;p&gt;改成 :（以下其中一个）&lt;/p&gt; &lt;p&gt;http://mirror.xmission.com/jenkins/updates/update-center.json&lt;/p&gt; &lt;p&gt;https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json&lt;/p&gt; &lt;p&gt;http://mirror.esuni.jp/jenkins/updates/update-center.json&lt;/p&gt; &lt;p&gt;，还是不行的话找到 updates 目录下的 default.json 把里面所有的谷歌地址改成百度的，即将 http://www.google.com/ 替换为 http://www.baidu.com/。输入密码后出现以下界面说明成功。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930110700695-1135323904.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;配置完成出现以下界面说明url配置完成！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930111616624-1110674914.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;3.推荐插件安装&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930111842790-18225033759be9c816bfa0cdb3.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;4.创建用户&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930115339754-873101586.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;5.实例配置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930115427702-1648350448.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;6.开始使用&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930115513633-1327651068.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;7.进入首页&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930115541147-1354444469.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;　至此、Jenkins的下载安装，完成，下面对Jenkins进行一系列的配置。&lt;/p&gt; &lt;h2&gt;五、Jenkins的配置&lt;/h2&gt; &lt;h3&gt;1.插件配置&lt;/h3&gt; &lt;p&gt;①**Maven Integration：&lt;strong&gt;新建job时有maven项目可以选择； ②&lt;/strong&gt;Deploy to container：&lt;strong&gt;将war包部署到tomcat所在的服务器上； ③&lt;/strong&gt;Publish Over SSH：**通过ssh推送文件，并可以执行shell命令；&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930123750344-416228695.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;2.Maven、Git、JDK配置&lt;/h3&gt; &lt;p&gt;　　点击：“系统管理”------&amp;gt;“全局工具配置”&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20190930185843004-1191682955.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;六、新建Jenkins任务&lt;/h2&gt; &lt;h3&gt;1.点击“系统管理”----&amp;gt;“系统设置”，进行ssh配置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008150032407-19838087102847f74164f08ecb.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;2.首页点击：“新建任务”&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008150220935-2070954311.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;3.General配置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008150440408-495419021.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;4.源码管理&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008150639895-853383437.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;5.构建触发器&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008151015911-546764062.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;6.Github配置WebHook,完成钩子程序的配置&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008151348924-34708835.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;7.构建环境&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152002454-872955647.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;启动脚本restart.sh如下:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008154942636-17598697153e21a7d7b3e0818b.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;echo &amp;quot;stop服务开始&amp;quot; pidlist=`ps -ef|grep jenkins-demo.jar | grep -v &amp;quot;grep&amp;quot;|awk '{print $2}'` #ps -u $USER|grep &amp;quot;Java&amp;quot;|grep -v &amp;quot;grep&amp;quot; if [ &amp;quot;$pidlist&amp;quot; = &amp;quot;&amp;quot; ]; then         echo &amp;quot;no jenkins-demo pid alive&amp;quot; else         echo &amp;quot;jenkins-demo Id list :$pidlist&amp;quot;         for pid in ${pidlist}         {                 kill -9 $pid                 echo &amp;quot;KILL $pid:&amp;quot;                 echo &amp;quot;service stop success&amp;quot;         } fi echo &amp;quot;stop服务脚本结束&amp;quot; echo &amp;quot;start服务脚本开始&amp;quot; JAVA_HOME=/usr/java/jdk1.8.0_181 dir=/opt/jenkins_jars cd $dir echo dir=$dir jar=$(find /lib -type f -name *.jar) classpath=$dir/*:$dir/lib/*:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar echo classpath=$classpath echo --------------------------------------------- nohup $JAVA_HOME/bin/java -classpath $classpath -XX:-UseGCOverheadLimit -Xms1024m -Xmx2048m -jar $dir/jenkins-demo.jar  &amp;gt; $dir/log/$(date +'%Y%m%d').log &amp;amp; echo &amp;quot;start服务脚本结束&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;8.构建&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152122095-970236536.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;　至此，第一个任务的配置完毕，接下来就是测试了！&lt;/p&gt; &lt;h2&gt;七、测试任务&lt;/h2&gt; &lt;h3&gt;1.立即构建&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152345366-457791086.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;2.控制台查看输出&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152424549-4227316.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;控制台的输出如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152522612-351062180.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;3.浏览器访问：http://47.92.236.212:8091/test&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152643396-12658318200f55994f8e317654.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;4.IDEA修改该JSP页面，并推送至git仓库&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008152842804-483480117.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;5.推送github后，触发钩子程序，jenkins自动构建任务&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008153150870-594628924.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;6.浏览器再次访问：http://47.92.236.212:8091/test&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008153444276-1111374948.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://www.cnblogs.com/rmxd/p/11609983.html#_labelTop" target="_blank"&gt;回到顶部&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;八、总结&lt;/h2&gt; &lt;p&gt;　　这里需要注意一下，如果只是单单部署war项目就比较简单了，配置如下：&lt;/p&gt; &lt;h3&gt;1.deploy to container&lt;/h3&gt; &lt;p&gt;　　&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008153856952-175315707.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;2.配置容器&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1733080-20191008154048514-447873848.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;转载于： https://www.cnblogs.com/rmxd/p/11609983.html&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 03:29:00 GMT</pubDate>
    </item>
    <item>
      <title>Linux Maven私服（Nexus）搭建</title>
      <link>https://maruifu.cn/article/257</link>
      <content:encoded>&lt;h1&gt;下载&lt;/h1&gt; &lt;p&gt;https://www.sonatype.com/download-oss-sonatype&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701211100759-1962733345.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;　推荐使用迅雷下载，用浏览器下载可能会失败。&lt;/p&gt; &lt;/blockquote&gt; &lt;h1&gt;安装&lt;/h1&gt; &lt;h2&gt;安装JDK&lt;/h2&gt; &lt;p&gt;安装JDK的过程就不在这里缀述了。&lt;/p&gt; &lt;h2&gt;安装 Nexus&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 解压下载的 Nexus Repository OSS tar -zxvf latest-unix.tar.gz # 解压会得到如下两个文件夹： nexus-3.23.0-03/ sonatype-work/  # 进入 nexus-**/bin/  # 编辑nexus.vmoptions 配置，我这里因为内存不够，我将运行内存改成了1G &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701211727459-301352960.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;启动 nexus&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;./nexus start &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701211848400-16230889070acf0a0a879252d1.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;这里启动很慢，我从启动到能够访问web页面，差不多花了3~5分钟&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;配置私服&lt;/h2&gt; &lt;h3&gt;添加阿里镜像到仓库组&lt;/h3&gt; &lt;p&gt;在浏览器访问 http://{IP}:8081(启动成功才能看到如下页面）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212223786-2057499165.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;根据提示，登录admin （密码有提示放在**/opt/sonatype-work/nexus3/admin.password**）进入设置向导，然后修改 admin 的密码，然后根据需要完成剩余向导&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;添加阿里镜像代理&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212540769-295687341.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212615902-1488075052.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212715247-1343250377.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;将新增的maven 代理添加到 maven-public 组&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212758000-1417785146e4f3a25039239796.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/1118330-20200701212826955-1202593326.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;配置maven setting.xml 下载私服jar包&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;settings xmlns=&amp;quot;http://maven.apache.org/SETTINGS/1.0.0&amp;quot;    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xsi:schemaLocation=&amp;quot;http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd&amp;quot;&amp;gt;   &amp;lt;!-- 本地仓地址 --&amp;gt;   &amp;lt;localRepository&amp;gt;D:\nexus\repository&amp;lt;/localRepository&amp;gt;    &amp;lt;!-- 配置用于鉴权的账号（由下面的“id”属性引用）--&amp;gt;   &amp;lt;servers&amp;gt;     &amp;lt;server&amp;gt;       &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;       &amp;lt;username&amp;gt;admin&amp;lt;/username&amp;gt;       &amp;lt;password&amp;gt;admin123&amp;lt;/password&amp;gt;     &amp;lt;/server&amp;gt;   &amp;lt;/servers&amp;gt;    &amp;lt;mirrors&amp;gt;     &amp;lt;mirror&amp;gt;       &amp;lt;!-- mirror的id要选定一个server的id，当拉取包时，会用server的id进行鉴权 --&amp;gt;       &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;       &amp;lt;mirrorOf&amp;gt;*&amp;lt;/mirrorOf&amp;gt;       &amp;lt;name&amp;gt;nexus maven&amp;lt;/name&amp;gt;       &amp;lt;!-- 私服地址，一般用maven-public分组仓库地址 --&amp;gt;       &amp;lt;url&amp;gt;http://192.168.10.128:8081/repository/maven-public/&amp;lt;/url&amp;gt;     &amp;lt;/mirror&amp;gt;   &amp;lt;/mirrors&amp;gt;      &amp;lt;!-- 配置 允许下载snapshot版本包（默认无法下载snapshot版本包）,         如果不配置profile无法从私服下载 SNAPSHOT 版本的包 --&amp;gt;   &amp;lt;profiles&amp;gt;     &amp;lt;profile&amp;gt;       &amp;lt;id&amp;gt;profile-1&amp;lt;/id&amp;gt;       &amp;lt;repositories&amp;gt;         &amp;lt;repository&amp;gt;           &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;           &amp;lt;name&amp;gt;local private nexus&amp;lt;/name&amp;gt;           &amp;lt;!-- 仓库的路径，我们只是使用public这个分组仓库 --&amp;gt;           &amp;lt;url&amp;gt;http://192.168.10.128:8081/repository/maven-public/&amp;lt;/url&amp;gt;           &amp;lt;releases&amp;gt;             &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;           &amp;lt;/releases&amp;gt;           &amp;lt;snapshots&amp;gt;             &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;           &amp;lt;/snapshots&amp;gt;         &amp;lt;/repository&amp;gt;       &amp;lt;/repositories&amp;gt;       &amp;lt;pluginRepositories&amp;gt;         &amp;lt;pluginRepository&amp;gt;           &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;           &amp;lt;name&amp;gt;local private nexus&amp;lt;/name&amp;gt;           &amp;lt;url&amp;gt;http://192.168.10.128:8081/repository/maven-public/&amp;lt;/url&amp;gt;           &amp;lt;releases&amp;gt;             &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;           &amp;lt;/releases&amp;gt;           &amp;lt;snapshots&amp;gt;             &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;           &amp;lt;/snapshots&amp;gt;         &amp;lt;/pluginRepository&amp;gt;       &amp;lt;/pluginRepositories&amp;gt;     &amp;lt;/profile&amp;gt;   &amp;lt;/profiles&amp;gt;    &amp;lt;activeProfiles&amp;gt;     &amp;lt;!-- 输入要激活的profile的ID --&amp;gt;     &amp;lt;activeProfile&amp;gt;profile-1&amp;lt;/activeProfile&amp;gt;   &amp;lt;/activeProfiles&amp;gt; &amp;lt;/settings&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置 pom.xml 发布包到私服&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;project&amp;gt;     &amp;lt;!-- ....省略部分内容 --&amp;gt;   &amp;lt;distributionManagement&amp;gt;     &amp;lt;repository&amp;gt;       &amp;lt;!-- 这里的id必须和 setting.xml中配置的server的id相同（用于鉴权） --&amp;gt;       &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;       &amp;lt;name&amp;gt;nexus-release&amp;lt;/name&amp;gt;       &amp;lt;url&amp;gt;http://192.168.10.128:8081/repository/maven-releases/&amp;lt;/url&amp;gt;     &amp;lt;/repository&amp;gt;     &amp;lt;!-- pom中版本如果以SNAPSHOT 结尾，则会被发布到 snapshot 仓库 --&amp;gt;     &amp;lt;snapshotRepository&amp;gt;       &amp;lt;id&amp;gt;nexus&amp;lt;/id&amp;gt;       &amp;lt;name&amp;gt;nexus-snapshot&amp;lt;/name&amp;gt;       &amp;lt;url&amp;gt;http://192.168.10.128:8081/repository/maven-snapshots/&amp;lt;/url&amp;gt;     &amp;lt;/snapshotRepository&amp;gt;   &amp;lt;/distributionManagement&amp;gt; &amp;lt;/project&amp;gt; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 03:12:24 GMT</pubDate>
    </item>
    <item>
      <title>Windows远程桌面出现CredSSP加密数据修正问题解决方案</title>
      <link>https://maruifu.cn/article/256</link>
      <content:encoded>&lt;h2&gt;问题现象&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105047408.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;h3&gt;方案一&lt;/h3&gt; &lt;p&gt;1、win+R打开运行窗口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105317414.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;2、输入gpedit.msc命令，点击“确定”&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105329174.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;3、依次展开“计算机配置”-&amp;gt;“管理模板”-&amp;gt;“系统”-&amp;gt;“凭据分配”设置名称： 加密数据库修正&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105246533.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;4、双击“加密数据库修正”，将状态改为“启用”，保护级别改为“易受攻击”，应用—&amp;gt;确定&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105406822.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;方案二&lt;/h3&gt; &lt;p&gt;1、win+R打开运行窗口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110110120578.png" alt="image-20221110110120578" title="image-20221110110120578" /&gt;&lt;/p&gt; &lt;p&gt;2、输入regedit命令，点击“确定”，打开注册表文件&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110110134570.png" alt="image-20221110110134570" title="image-20221110110134570" /&gt;&lt;/p&gt; &lt;p&gt;3、在注册表中进入如下路径：HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\CredSSP\Parameters&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110110210933.png" alt="image-20221110110210933" title="image-20221110110210933" /&gt;&lt;/p&gt; &lt;p&gt;4、右击Parameters—&amp;gt;新建—&amp;gt;DWORD（32）位，命名为AllowEncryptionOracle&lt;/p&gt; &lt;p&gt;注意：如果此文件已存在，则不需要创建，如果不存在，则创建&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110110232409.png" alt="image-20221110110232409" title="image-20221110110232409" /&gt;&lt;/p&gt; &lt;p&gt;5、双击刚新建的AllowEncryptionOracle，将数值数据修改为2&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110110246284.png" alt="image-20221110110246284" title="image-20221110110246284" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/20200520105627958.png" alt="img" title="img" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 03:04:13 GMT</pubDate>
    </item>
    <item>
      <title>VMWare Workstation无法生成SSL密钥导致安装失败问题</title>
      <link>https://maruifu.cn/article/255</link>
      <content:encoded>&lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;一台安装有Windows 8 Enterprise x64系统的计算机，试图安装VMware Workstation 15.5时报错“Setup failed to generate the SSL keys necessary to run VMWare Server. Click Ok to cancel this installation.（大意为：安装程序无法生成VMware Server所需的SSL密钥，请单击‘确定’，终止安装程序。）”。&lt;/p&gt; &lt;h2&gt;可能的原因&lt;/h2&gt; &lt;p&gt;查阅资料显示，这一问题可能与错误的系统时间、DirectX配置不正确、没有正确安装的Visual C++运行库、PATH环境变量中包含指向VMware不支持的OpenSSL程序版本的路径等问题有关。&lt;/p&gt; &lt;h2&gt;解决方案&lt;/h2&gt; &lt;p&gt;经排查，VMware Workstation 15.5的安装程序会自动安装Microsoft Visual C++ 2015-2019 Redistributable，但似乎存在问题，可以先从 [控制面板] &amp;gt; [程序和功能] 页面删除Microsoft Visual C++ 2015-2019 Redistributable (x86)和Microsoft Visual C++ 2015-2019 Redistributable (x64)，然后从 Microsoft下载中心下载Microsoft Visual C++ 2015 Redistributable Update 3 (x86)和Microsoft Visual C++ 2015 Redistributable Update 3 (x64)并安装。随后，重新执行VMware Workstation安装程序，问题解决。&lt;/p&gt; &lt;h2&gt;参考资料&lt;/h2&gt; &lt;p&gt;https://kb.vmware.com/s/article/1008179&lt;/p&gt; &lt;p&gt;http://element-ui.cn/article/show-85929.aspx&lt;/p&gt; &lt;p&gt;https://www.cnblogs.com/d9394/p/13776623.html&lt;/p&gt; &lt;p&gt;https://blog.csdn.net/weixin_37949588/article/details/106306033&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 02:57:48 GMT</pubDate>
    </item>
    <item>
      <title>Unsupported major.minor version 52.0解决办法</title>
      <link>https://maruifu.cn/article/254</link>
      <content:encoded>&lt;h2&gt;1.首先解释一下报错原因：&lt;/h2&gt; &lt;p&gt;stanford parser和jdk版本对应关系&lt;/p&gt; &lt;pre&gt;&lt;code&gt;J2SE 8 = 52, J2SE 7 = 51, J2SE 6.0 = 50, J2SE 5.0 = 49, JDK 1.4 = 48, JDK 1.3 = 47, JDK 1.2 = 46, JDK 1.1 = 45 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Unsupported major.minor version 52.0: 看到Unsupported你是不是会想到jdk高版本能兼容低版本，但是低版本不能兼容高版本，不错，猜对了，其实就是这个意思。这个错误意思是你项目用JDK1.8运行过，现在又在本地的eclipse等开发工具或者本地环境变量为低版本的jdk1.7或者jdk1.6下运行，eclipse会说：“抱歉，我本地jdk版本太低，不支持这个高级版本jdk1.8编译过的项目运行”。&lt;/p&gt; &lt;p&gt;你看看你本地是报52还是51或者其他的错。&lt;/p&gt; &lt;h2&gt;2.配置jdk解决问题：&lt;/h2&gt; &lt;p&gt;这几个地方jdk要一致：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;看看你系统的jdk环境变量配置的是jdk那个版本&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Windows—— Preferences——Java——Compiler——设为jdk1.8&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在此页面的Java——Installed JREs——设为jdk1.8&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;关闭此页面，项目右键（或者core包）——Build Path（也可是Properties）——Configure Build Path——Java Build Path——Libraries下面的JRE System Library改为jdk1.8（此处设置见参考3），保证旁边的Order and Export这个jdk与之相同&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;同页面的Java Compiler选项改为jdk1.8&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;同页面的Project Facets——右侧的Java改为1.8（此项也可以不改）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果还不行看看你的Eclipse的Server里配置的tomcat和jdk版本是否不同：&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Windows——Preference——Server——Runtime Environments——点击你要用的tomcat——点击Edit ——在弹出面板的JRE中选择和你项目对应的jdk版本&lt;/p&gt; &lt;ol start="8"&gt; &lt;li&gt;如果上述还不管用的话试试：&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;修改org.eclipse.wst.common.project.facet.core.xml： 打开项目所在的文件夹，打开.settings文件夹，修改里面的“org.eclipse.wst.common.project.facet.core.xml”文件&lt;/p&gt; &lt;p&gt;如我的路径是：D:\cctv5cms\maven.1490956540309\cms-cms\cms-core.settings\org.eclipse.wst.common.project.facet.core.xml&lt;/p&gt; &lt;p&gt;本文链接：http://blog.csdn.net/superit401/article/details/72731381&lt;/p&gt; &lt;p&gt;参考1：https://www.zifangsky.cn/600.html&lt;/p&gt; &lt;p&gt;参考2：http://bbs.csdn.net/topics/391873068&lt;/p&gt; &lt;p&gt;参考3：http://www.oschina.net/question/207494_84715&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 02:52:46 GMT</pubDate>
    </item>
    <item>
      <title>手把手教你在Windows 10安装MySQL 8.0（详细图文）</title>
      <link>https://maruifu.cn/article/253</link>
      <content:encoded>&lt;h2&gt;1. 官网下载安装包&lt;/h2&gt; &lt;p&gt;下载地址： https://dev.mysql.com/downloads/mysql/&lt;/p&gt; &lt;p&gt;通过下载页面可以选择安装包或者是压缩包。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110103613740.png" alt="image-20221110103613740" title="image-20221110103613740" /&gt;&lt;/p&gt; &lt;p&gt;这里选择安装文件（Installer MSI）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110103701987.png" alt="image-20221110103701987" title="image-20221110103701987" /&gt;&lt;/p&gt; &lt;h2&gt;2. 点击下载的程序包安装&lt;/h2&gt; &lt;p&gt;2.1 选择安装类型&lt;/p&gt; &lt;p&gt;选择【Server only】然后【Next】&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110103940858.png" alt="image-20221110103940858" title="image-20221110103940858" /&gt;&lt;/p&gt; &lt;p&gt;2.2 安装前提检查&lt;/p&gt; &lt;p&gt;如果电脑上之前没有安装Microsoft Visual C++环境的话， 会自动弹出MS Visual C++的安装页面，进行安装即可。&lt;/p&gt; &lt;p&gt;点击【Execute】&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104001725.png" alt="image-20221110104001725" title="image-20221110104001725" /&gt;&lt;/p&gt; &lt;p&gt;安装MS Visual C++:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104018896.png" alt="image-20221110104018896" title="image-20221110104018896" /&gt;&lt;/p&gt; &lt;p&gt;设置成功后，点击【关闭】：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104034362.png" alt="image-20221110104034362" title="image-20221110104034362" /&gt;&lt;/p&gt; &lt;h3&gt;2.3 再次安装前提检查&lt;/h3&gt; &lt;p&gt;MS Visual C++的安装后，再次进入安装前提检查页面，继续安装MySQL。&lt;/p&gt; &lt;p&gt;点击【Next】:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104108376.png" alt="image-20221110104108376" title="image-20221110104108376" /&gt;&lt;/p&gt; &lt;p&gt;点击【Execute】:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104126501.png" alt="image-20221110104126501" title="image-20221110104126501" /&gt;&lt;/p&gt; &lt;p&gt;点击【Next】:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104144567.png" alt="image-20221110104144567" title="image-20221110104144567" /&gt;&lt;/p&gt; &lt;h3&gt;2.4 High Availability选择&lt;/h3&gt; &lt;p&gt;选择【Standalone】选项&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104200624.png" alt="image-20221110104200624" title="image-20221110104200624" /&gt;&lt;/p&gt; &lt;h3&gt;2.5 端口号等设置&lt;/h3&gt; &lt;p&gt;端口号等保持默认即可,点击【Next】。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104216829.png" alt="image-20221110104216829" title="image-20221110104216829" /&gt;&lt;/p&gt; &lt;h3&gt;2.6 认证方式等设置&lt;/h3&gt; &lt;p&gt;选择推荐方式，点击【Next】。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104237131.png" alt="image-20221110104237131" title="image-20221110104237131" /&gt;&lt;/p&gt; &lt;h3&gt;2.7 Root密码等设置&lt;/h3&gt; &lt;p&gt;设置MySQL root用户的登录密码。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104255842.png" alt="image-20221110104255842" title="image-20221110104255842" /&gt;&lt;/p&gt; &lt;p&gt;这里输入密码：root&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104312746.png" alt="image-20221110104312746" title="image-20221110104312746" /&gt;&lt;/p&gt; &lt;h3&gt;2.8 Windowns服务注册&lt;/h3&gt; &lt;p&gt;点击【Next】:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104327744.png" alt="image-20221110104327744" title="image-20221110104327744" /&gt;&lt;/p&gt; &lt;h3&gt;2.9 安装过程等待&lt;/h3&gt; &lt;p&gt;点击【Next】:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104345323.png" alt="image-20221110104345323" title="image-20221110104345323" /&gt;&lt;/p&gt; &lt;h3&gt;2.10 安装结束&lt;/h3&gt; &lt;p&gt;点击【Next】，结束安装。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104415040.png" alt="image-20221110104415040" title="image-20221110104415040" /&gt;&lt;/p&gt; &lt;h3&gt;2.11 安装后产品配置&lt;/h3&gt; &lt;p&gt;点击【Next】：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104434903.png" alt="image-20221110104434903" title="image-20221110104434903" /&gt;&lt;/p&gt; &lt;h3&gt;2.12 保存安装日志。&lt;/h3&gt; &lt;p&gt;点击【Copy Log to Clipboard】,保存安装日志方便安装问题的调查。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104447367.png" alt="image-20221110104447367" title="image-20221110104447367" /&gt;&lt;/p&gt; &lt;h2&gt;3. 安装成功后，验证&lt;/h2&gt; &lt;p&gt;通过开始菜单（Win键）调用MySQL命令行客户端（MySQL Command Line Client），就可以进行MySQL操作了。&lt;/p&gt; &lt;p&gt;点击开始菜单（Win键），找到安装的MySQL文件夹。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104544593.png" alt="image-20221110104544593" title="image-20221110104544593" /&gt;&lt;/p&gt; &lt;p&gt;点击【MySQL 8.0 Command Line Client】,然后输入密码开始MySQL之旅。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104558092.png" alt="image-20221110104558092" title="image-20221110104558092" /&gt;&lt;/p&gt; &lt;h2&gt;4. MySQL的路径设置&lt;/h2&gt; &lt;p&gt;按照上面的方法安装MySQL后，可以通过开始菜单（Win键）调用MySQL命令行，也可以通过Windows的【命令提示符】调用MySQL监视器对MySQL进行操作。 默认安装情况下MySQL监视器是放在以下路径中：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;C:\Program Files\MySQL\MySQL Server 8.0\bin &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;所以，要想启动的话，需要先移动到这个文件夹中。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/10/image-20221110104630367.png" alt="image-20221110104630367" title="image-20221110104630367" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;在【系统变量】或者【用户变量】中，选择变量【Path】，点击【编辑】-&amp;gt;【新建】，输入上面的MySQL路径“C:\Program Files\MySQL\MySQL Server 8.0\bin”。这样，以后在Windows命令行中调用MySQL命令行就不用每次都移动文件夹了。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;转载于：https://cloud.tencent.com/developer/article/1636375&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 10 Nov 2022 02:50:18 GMT</pubDate>
    </item>
    <item>
      <title>jquery中load中文乱码的解决方法</title>
      <link>https://maruifu.cn/article/252</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;$(&amp;quot;&amp;quot;).load()中文乱码问题&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;JSP 请求页面&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$(&amp;quot;#baseLineThreeCenterTab&amp;quot;).load(basePath+&amp;quot;/pro/baseline/gotoProConfigBaseLineMain.action?parameter=&amp;quot;+parameter);   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;java后台接收的如：parameter=“中文”，则会出现乱码。&lt;/p&gt; &lt;p&gt;解决方法：&lt;/p&gt; &lt;h2&gt;JSP页面&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;// encodeURI()编码  $(&amp;quot;#baseLineThreeCenterTab&amp;quot;).load(basePath+&amp;quot;/pro/baseline/gotoProConfigBaseLineMain.action?parameter=&amp;quot;+encodeURI(parameter)); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;JAVA:&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;// URLDecoder.decode(parameter, &amp;quot;utf-8&amp;quot;);解码 import java.net.URLDecoder; String parameter = URLDecoder.decode(parameter, &amp;quot;utf-8&amp;quot;); &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 07 Nov 2022 02:28:00 GMT</pubDate>
    </item>
    <item>
      <title>Chrome浏览器内置翻译无法使用,右键翻译无反应?（已失效）</title>
      <link>https://maruifu.cn/article/251</link>
      <content:encoded>&lt;h2&gt;原因&lt;/h2&gt; &lt;p&gt;谷歌(Google)以使用率低为由，停止了Google翻译在中国大陆的服务，Google翻译退出中国,仅存唯一功能也没了.&lt;/p&gt; &lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/10/05/2022-10-05-3.21.39.png" alt="截屏2022-10-05 下午3.21.39" title="截屏2022-10-05 下午3.21.39" /&gt;&lt;/p&gt; &lt;p&gt;点击翻译无反应,依然显示英文.&lt;/p&gt; &lt;h2&gt;分析&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/10/05/2022-10-05-3.10.58.png" alt="截屏2022-10-05 下午3.10.58" title="截屏2022-10-05 下午3.10.58" /&gt;&lt;/p&gt; &lt;p&gt;我们通过抓包工具可以看见翻译的时候会访问 &lt;code&gt;https://translate.googleapis.com&lt;/code&gt;&lt;/p&gt; &lt;p&gt;translate.googleapis.com（Google 翻译 API）,因为停止了中国大陆的服务,不再分配中国内地的服务器地址，所以浏览器内置的 Google 翻译无法使用&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;浏览器内置API不支持扩展配置的socks5代理，所以挂梯子是无效的，除非用系统代理。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;解决方法&lt;/h2&gt; &lt;p&gt;Google 在中国开展业务的相关网站所使用的 IP 地址都是共享的，包括谷歌翻译业务在内，因此只要能找到你能正常访问的 Google 服务的相关域名，比如&lt;a href="https://www.google.cn/" target="_blank"&gt;谷歌中国主页&lt;/a&gt;、能够在网页上正常加载的 Adsense 广告或 Analytics 统计所使用的 JavaScript 脚本文件网址等，就可以很轻松的获取到在你所在的网络环境中可以正常使用的 IP 地址。获取到可用 IP 地址后，将其添加到操作系统的 hosts 文件，使其映射到谷歌翻译 API 所使用的域名，Chrome 翻译功能就能正常使用了。&lt;/p&gt; &lt;p&gt;下面是获取可用 IP 地址以及修改 Windows 系统和 macOS 系统 hosts 文件的具体方法。&lt;/p&gt; &lt;h3&gt;手动&lt;/h3&gt; &lt;p&gt;可以编辑HOSTS文件对域名的地址解析进行修正&lt;/p&gt; &lt;p&gt;由于 &lt;a href="http://translate.googleapis.com/" target="_blank"&gt;translate.googleapis.com&lt;/a&gt; 采用与 &lt;a href="http://google.cn/" target="_blank"&gt;google.cn&lt;/a&gt; 相同的 IP地址，可以先ping一下得到对应的IP再修改HOSTS文件。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 打开终端或者CMD 得到IP地址 ping google.cn  # windows C:\Windows\System32\drivers\etc\hosts # Linux / MacOS  /etc/hosts  # 在文件中添加一行保存即可，格式示例（自己根据通过 `ping google.cn` 得到的地址修改前面的IP地址）： 114.250.65.34 translate.googleapis.com &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;自动&lt;/h3&gt; &lt;h4&gt;windows&lt;/h4&gt; &lt;p&gt;为简化操作，已手动修改 hosts 文件的步骤写成了批处理脚本，只需一键即可完成所有修改步骤。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;下载批处理脚本&lt;/strong&gt;：&lt;a href="https://nas.mrf.ink:3000/tools/shell/raw/master/browser/fix-google-translate-cn/windos.bat" target="_blank"&gt;fix-google-translate-cn.bat&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;使用方法很简单，下载完成并解压缩，在批处理文件上点击右键，在弹出的菜单中点击【&lt;strong&gt;以管理员身份运行&lt;/strong&gt;】即可。如果看到如下所示提示，表示规则添加成功，Chrome 翻译就恢复正常了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Adding the rule &amp;quot;114.250.65.34 translate.googleapis.com&amp;quot; Done. 请按任意键继续... &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此脚本可以重复使用。添加规则后再次使用时会出现交互提示信息，输入 &lt;strong&gt;1&lt;/strong&gt; 会尝试更新已添加规则的 IP 地址，如果没有变化则不做任何修改，输入 &lt;strong&gt;2&lt;/strong&gt; 会删除已添加的规则。&lt;/p&gt; &lt;h4&gt;linux&amp;amp;MacOS&lt;/h4&gt; &lt;p&gt;为简化操作，已将手动修改 hosts 文件的步骤写成了 Shell 脚本，只需一键即可完成所有修改步骤。&lt;/p&gt; &lt;p&gt;打开“&lt;strong&gt;终端&lt;/strong&gt;”，拷贝以下命令并将其粘贴到终端上，按回车，输入你的系统密码，再按回车。注意，输入密码时是不显示任何信息的，只要确保输入的密码是正确的就可以。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sudo bash -c &amp;quot;$(curl -skL https://nas.mrf.ink:3000/tools/shell/raw/master/browser/fix-google-translate-cn/mac&amp;amp;linux.sh)&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果看到如下所示提示，表示规则添加成功，也就可以正常使用 Chrome 的谷歌翻译功能了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Adding the rule &amp;quot;114.250.65.34 translate.googleapis.com&amp;quot; Done. &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;结果&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/10/05/2022-10-05-3.23.50.png" alt="截屏2022-10-05 下午3.23.50" title="截屏2022-10-05 下午3.23.50" /&gt;&lt;/p&gt; &lt;p&gt;可以看出再次翻译显示中文了.&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 05 Oct 2022 07:27:00 GMT</pubDate>
    </item>
    <item>
      <title>大批量更新数据mysql批量更新的四种方法</title>
      <link>https://maruifu.cn/article/250</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;mysql 批量更新如果一条条去更新效率是相当的慢, 循环一条一条的更新记录,一条记录update一次，这样性能很差，也很容易造成阻塞。mysql 批量更新共有四种办法&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;&lt;strong&gt;replace into 批量更新&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;replace into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y'); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;insert into ...on duplicate key update批量更新&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;insert into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y') on duplicate key update dr=values(dr); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;创建临时表，先更新临时表，然后从临时表中update&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;create temporary table tmp(id int(4) primary key,dr varchar(50)); insert into tmp values  (0,'gone'), (1,'xx'),...(m,'yy'); update test_tbl, tmp set test_tbl.dr=tmp.dr where test_tbl.id=tmp.id; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注意：这种方法需要用户有temporary 表的create 权限。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;使用mysql 自带的语句构建批量更新&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;mysql 实现批量 可以用点小技巧来实现:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;UPDATE tableName     SET orderId = CASE id          WHEN 1 THEN 3          WHEN 2 THEN 4          WHEN 3 THEN 5      END WHERE id IN (1,2,3) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这句sql 的意思是，更新orderId 字段，如果id=1 则orderId 的值为3，如果id=2 则orderId 的值为4…… where部分不影响代码的执行，但是会提高sql执行的效率。确保sql语句仅执行需要修改的行数，这里只有3条数据进行更新，而where子句确保只有3行数据执行。&lt;/p&gt; &lt;p&gt;如果更新多个值的话，只需要稍加修改：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-javascript"&gt;UPDATE categories      SET orderId = CASE id          WHEN 1 THEN 3          WHEN 2 THEN 4          WHEN 3 THEN 5      END,      title = CASE id          WHEN 1 THEN 'New Title 1'         WHEN 2 THEN 'New Title 2'         WHEN 3 THEN 'New Title 3'     END WHERE id IN (1,2,3) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;到这里，已经完成一条mysql语句更新多条记录了。&lt;/p&gt; &lt;h3&gt;总结&lt;/h3&gt; &lt;p&gt;更新 100000条数据的性能就测试结果来看，测试当时使用replace into性能较好。&lt;/p&gt; &lt;p&gt;&lt;code&gt;replace into&lt;/code&gt; 和 &lt;code&gt;insert into on duplicate key update&lt;/code&gt;的不同在于：&lt;/p&gt; &lt;p&gt;&lt;code&gt;replace into&lt;/code&gt; 操作本质是对重复的记录先&lt;code&gt;delete&lt;/code&gt; 后&lt;code&gt;insert&lt;/code&gt;，如果更新的字段不全会将缺失的字段置为缺省值，用这个要悠着点否则不小心清空大量数据可不是闹着玩的。 &lt;code&gt;insert into&lt;/code&gt; 则是只&lt;code&gt;update&lt;/code&gt;重复记录，不会改变其它字段。&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 09 Sep 2022 08:44:00 GMT</pubDate>
    </item>
    <item>
      <title>confluence 忘记密码</title>
      <link>https://maruifu.cn/article/249</link>
      <content:encoded>&lt;h2&gt;数据库相关信息&lt;/h2&gt; &lt;p&gt;查看 &lt;code&gt;confluence.home&lt;/code&gt; 配置&lt;/p&gt; &lt;pre&gt;&lt;code class="language-kotlin"&gt;cat /opt/atlassian/confluence/confluence/WEB-INF/classes/confluence-init.properties | grep confluence.home &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/09/09/image-20220909163918541.png" alt="image-20220909163918541" title="image-20220909163918541" /&gt;&lt;/p&gt; &lt;p&gt;在 &lt;code&gt;confluence.home&lt;/code&gt; 下面的 &lt;code&gt;confluence.cfg.xml&lt;/code&gt; 文件里面查看数据库信息&lt;/p&gt; &lt;pre&gt;&lt;code class="language-kotlin"&gt; cat /var/atlassian/application-data/confluence/confluence.cfg.xml  | grep connection &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/09/09/image-20220909163931535.png" alt="image-20220909163931535" title="image-20220909163931535" /&gt;&lt;/p&gt; &lt;h2&gt;修改管理员密码&lt;/h2&gt; &lt;p&gt;登录数据库&lt;/p&gt; &lt;pre&gt;&lt;code class="language-undefined"&gt;mysql -hlocalhost -uconfluence -p confluence &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在数据库里面查看管理员用户&lt;/p&gt; &lt;pre&gt;&lt;code class="language-csharp"&gt;select u.id, u.user_name, u.active from cwd_user u   join cwd_membership m on u.id=m.child_user_id join cwd_group g on m.parent_id=g.id join cwd_directory d on d.id=g.directory_id   where g.group_name = 'confluence-administrators' and d.directory_name='Confluence Internal Directory';   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/09/09/image-20220909163949048.png" alt="image-20220909163949048" title="image-20220909163949048" /&gt;&lt;/p&gt; &lt;p&gt;更新管理员用户密码为 &lt;code&gt;admin&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;update cwd_user set credential =   'x61Ey612Kl2gpFL56FT9weDnpSo4AV8j8+qx2AuTHdRyY036xxzTTrw10Wq3+4qQyB+XURPWx1ONxp3Y3pB37A=='   where id=xxxxxx; # 这里需要改成 393217 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意此处xxxxxx 为上一步的 id&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;如果你的密码是{PKCS5S2}前缀开头的，则用下面这个sql:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;update cwd_user set credential = '{PKCS5S2}ltrb9LlmZ0QDCJvktxd45WgYLOgPt2XTV8X7av2p0mhPvIwofs9bHYVz2OXQ6/kF' where id=xxxxxx; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个管理员密码为 &lt;code&gt;Ab123456&lt;/code&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 09 Sep 2022 08:40:00 GMT</pubDate>
    </item>
    <item>
      <title>使用javapackager打包各系统安装包</title>
      <link>https://maruifu.cn/article/248</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;在平时我们打包会将其打成Jar，那么在其他平台运行的时候就需要安装jre来支持运行。我们用的是javapackager,javapackager是jdk1.8自带的一个打包工具，可以生成各个系统的安装包&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;准备工作&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://img.maruifu.com/images/blog/file/innosetup-5.6.0.exe" target="_blank"&gt;innosetup-5.6.0.exe&lt;/a&gt;（windows下Java8支持6版本以下的，不要下载6及其6以上的版本，否则无法打包成功）&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/wixtoolset/wix3/releases" target="_blank"&gt;wix&lt;/a&gt;（打包成msi必须下载，没有下载&lt;code&gt;javapackager&lt;/code&gt;会提示缺少wix）&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.oracle.com/java/technologies/downloads/#java8" target="_blank"&gt;JDK8&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;用法&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;javapackager  command  [options] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;command :应该执行的任务&lt;/p&gt; &lt;p&gt;[options] :以空格分隔的命令的一个或多个选项&lt;/p&gt; &lt;h2&gt;Commands&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;您可以指定以下命令之一。在命令之后，指定它的选项。&lt;/p&gt; &lt;/blockquote&gt; &lt;ol&gt; &lt;li&gt;&lt;code&gt;-createbss&lt;/code&gt;:将 CSS 文件转换为二进制形式。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-createjar&lt;/code&gt;:根据其他参数生成 JAR 存档。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-deploy&lt;/code&gt;:组装应用程序包以进行重新分发。默认情况下，部署任务会生成基础应用程序包，但如果需要，它也可以生成自包含的应用程序包。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-makeall&lt;/code&gt;:使用预定义的大多数参数，一次调用执行编译、&lt;code&gt;createjar&lt;/code&gt;和&lt;code&gt;deploy&lt;/code&gt;步骤，并尝试生成所有适用的自包含应用程序包。源文件必须位于名为 的文件夹&lt;code&gt;src&lt;/code&gt;中，生成的文件（JAR、JNLP、HTML 和自包含应用程序包）位于名为 的文件夹中&lt;code&gt;dist&lt;/code&gt;。此命令只能以最少的方式配置，并且尽可能自动化。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-signjar&lt;/code&gt;:使用提供的证书对 JAR 文件进行签名。&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;createbss 命令的选项&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-outdir dir&lt;/code&gt;: 将接收生成的输出文件的目录的名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcdir dir&lt;/code&gt;:要打包的文件的基本目录。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcfiles files&lt;/code&gt;:&lt;code&gt;-srcdir&lt;/code&gt;选项指定的目录中的文件列表。如果省略，将使用目录中的所有文件（在这种情况下这是一个强制参数）。列表中的文件必须用空格分隔。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;createjar 命令的选项&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-appclass app-class&lt;/code&gt;:要执行的应用程序类的限定名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-argument arg&lt;/code&gt;:要作为&lt;code&gt;&amp;lt;fx:argument&amp;gt;&lt;/code&gt;元素插入到 JNLP 文件中的未命名参数。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-classpath files&lt;/code&gt;:相关 JAR 文件名列表。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-manifestAttrs manifest-attributes&lt;/code&gt;:其他清单属性的名称和值列表。句法：“名称 1=值 1，名称 2=值 2，名称 3=值 3”&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-nocss2bin&lt;/code&gt;:打包器在复制到 JAR 之前不会将 CSS 文件转换为二进制形式。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-outdir dir&lt;/code&gt;:将接收生成的输出文件的目录的名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-outfile filename&lt;/code&gt;:将生成的文件的名称（不带扩展名）。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-paramfile file&lt;/code&gt;:具有默认命名应用程序参数的属性文件。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-preloader preloader-class&lt;/code&gt;:要执行的 JavaFX 预加载器类的限定名称。此选项仅用于 JavaFX 应用程序。不要用于 Java 应用程序，包括无头应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcdir dir&lt;/code&gt;:要打包的文件的基本目录。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcfiles files&lt;/code&gt;:&lt;code&gt;-srcdir&lt;/code&gt;选项指定的目录中的文件列表。如果省略，将使用目录中的所有文件（在这种情况下这是一个强制参数）。列表中的文件必须用空格分隔。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;deploy命令的选项&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-allpermissions&lt;/code&gt;:如果存在，应用程序将需要 JNLP 文件中的所有安全权限。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-appclass app-class&lt;/code&gt;:要执行的应用程序类的限定名称。 就是详细包名+类名，也就是程序的入口类的全类名&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-argument arg&lt;/code&gt;:要插入到&lt;a href="fx:argument" target="_blank"&gt;fx:argument&lt;/a&gt;JNLP 文件中的元素中的未命名参数。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-Bbundler-argument=value&lt;/code&gt;: 向用于打包自包含应用程序的捆绑程序提供信息。有关每个捆绑器的参数的信息，请参阅&lt;a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javapackager.html#BGBDJIGE" target="_blank"&gt;自包含应用程序捆绑器的参数。&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-callbacks&lt;/code&gt;:在生成的 HTML 中指定用户回调方法。格式如下：“名称 1：值 1，名称 2：值 2，...”&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-description description&lt;/code&gt;:应用程序的描述。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-embedCertificates&lt;/code&gt;:如果存在，证书将嵌入 JNLP 文件中。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-embedjnlp&lt;/code&gt;:如果存在，JNLP 文件将嵌入到 HTML 文档中。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-height height&lt;/code&gt;:应用程序的高度。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-htmlparamfile file&lt;/code&gt;:属性文件，其中包含在浏览器中运行时生成的应用程序的参数。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-isExtension&lt;/code&gt;:如果存在，则将&lt;code&gt;srcfiles&lt;/code&gt;其视为扩展。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-name name&lt;/code&gt;:应用程序的名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-native type&lt;/code&gt;:生成独立的应用程序包（如果可能）。使用该&lt;code&gt;-B&lt;/code&gt;选项为正在使用的捆绑器提供参数。如果指定了类型，则仅创建此类型的捆绑包。如果未指定类型，&lt;code&gt;all&lt;/code&gt;则使用。&lt;/p&gt; &lt;p&gt;以下值对type有效：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;all&lt;/code&gt;：为运行它的平台运行所有安装程序，并为应用程序创建磁盘映像。如果未指定类型，则使用此值。&lt;/li&gt; &lt;li&gt;&lt;code&gt;installer&lt;/code&gt;：运行它所在平台的所有安装程序。&lt;/li&gt; &lt;li&gt;&lt;code&gt;image&lt;/code&gt;：为应用程序创建磁盘映像。创建原生的镜像(打成window的exe)。&lt;/li&gt; &lt;li&gt;&lt;code&gt;exe&lt;/code&gt;: 生成一个 Windows&lt;code&gt;.exe&lt;/code&gt;包。&lt;/li&gt; &lt;li&gt;&lt;code&gt;msi&lt;/code&gt;：生成一个 Windows 安装程序包。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-outdir dir&lt;/code&gt;:将接收生成的输出文件的目录的名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-outfile filename&lt;/code&gt;:将生成的文件的名称（不带扩展名）。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-paramfile file&lt;/code&gt;:具有默认命名应用程序参数的属性文件。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-preloader preloader-class&lt;/code&gt;:要执行的 JavaFX 预加载器类的限定名称。此选项仅用于 JavaFX 应用程序。不要用于 Java 应用程序，包括无头应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcdir dir&lt;/code&gt;:要打包的文件的基本目录。就是我们之前包含jar文件的文件夹(注意这里不是java源代码目录)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-srcfiles files&lt;/code&gt;:&lt;code&gt;-srcdir&lt;/code&gt;选项指定的目录中的文件列表。如果省略，将使用目录中的所有文件（在这种情况下这是一个强制参数）。列表中的文件必须用空格分隔。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;20 &lt;code&gt;-templateId&lt;/code&gt;:模板处理应用的应用ID。&lt;/p&gt; &lt;p&gt;21 &lt;code&gt;-templateInFilename&lt;/code&gt;:HTML 模板文件的名称。占位符采用以下形式：&lt;code&gt;#XXXX.YYYY(APPID)#&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 其中 APPID 是应用程序的标识符，XXX 是以下之一： &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;DT.SCRIPT.URL&lt;/code&gt;dtjava.js 在部署工具包中的位置。默认情况下，位置是http://java.com/js/dtjava.js&lt;/li&gt; &lt;li&gt;&lt;code&gt;DT.SCRIPT.CODE&lt;/code&gt;包含部署工具包的 dtjava.js 的脚本元素。&lt;/li&gt; &lt;li&gt;&lt;code&gt;DT.EMBED.CODE.DYNAMIC&lt;/code&gt;将应用程序嵌入给定占位符的代码。预计代码将被包装在&lt;code&gt;function()&lt;/code&gt;方法中。&lt;/li&gt; &lt;li&gt;&lt;code&gt;DT.EMBED.CODE.ONLOAD&lt;/code&gt;使用钩子将应用程序嵌入网页所需的所有代码&lt;code&gt;onload&lt;/code&gt;（包含 dtjava.js 除外）。&lt;/li&gt; &lt;li&gt;&lt;code&gt;DT.LAUNCH.CODE&lt;/code&gt;启动应用程序所需的代码。预计代码将被包装在&lt;code&gt;function()&lt;/code&gt;方法中。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;22 &lt;code&gt;-templateOutFilename&lt;/code&gt;:将从模板生成的 HTML 文件的名称。&lt;/p&gt; &lt;p&gt;23 &lt;code&gt;-title title&lt;/code&gt;:应用程序的标题。&lt;/p&gt; &lt;p&gt;24 &lt;code&gt;-vendor vendor&lt;/code&gt;:应用程序的供应商。&lt;/p&gt; &lt;p&gt;25 &lt;code&gt;-width width&lt;/code&gt;:应用程序的宽度。&lt;/p&gt; &lt;p&gt;26 &lt;code&gt;-updatemode update-mode&lt;/code&gt;:设置 JNLP 文件的更新模式。&lt;/p&gt; &lt;h2&gt;makeall 命令的选项&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-appclass app-class&lt;/code&gt;:要执行的应用程序类的限定名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-classpath files&lt;/code&gt;:相关 JAR 文件名列表。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-height height&lt;/code&gt;:应用程序的高度。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-name name&lt;/code&gt;:应用程序的名称。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-preloader preloader-class&lt;/code&gt;:要执行的 JavaFX 预加载器类的限定名称。此选项仅用于 JavaFX 应用程序。不要用于 Java 应用程序，包括无头应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;-width width&lt;/code&gt;:应用程序的宽度。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;signjar 命令的选项&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;&lt;code&gt;-alias&lt;/code&gt;:密钥的别名。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-keyPass&lt;/code&gt;:用于恢复密钥的密码。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-keyStore file&lt;/code&gt;:密钥库文件名。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-outdir dir&lt;/code&gt;:将接收生成的输出文件的目录的名称。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-srcdir dir&lt;/code&gt;:要签名的文件的基本目录。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-srcfiles files&lt;/code&gt;:&lt;code&gt;-srcdir&lt;/code&gt;选项指定的目录中的文件列表。如果省略，将使用目录中的所有文件（在这种情况下这是一个强制参数）。列表中的文件必须用空格分隔。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-storePass&lt;/code&gt;:检查密钥库完整性或解锁密钥库的密码&lt;/li&gt; &lt;li&gt;&lt;code&gt;-storeType&lt;/code&gt;:密钥库类型。默认值为“jks”。&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;应用程序捆绑的参数&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;这些自定义的bundle参数在使用的时候要注意，-B加上参数名=值 例如 icon 使用的时候就是 -Bicon=“path&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;该命令的&lt;code&gt;-B&lt;/code&gt;选项用于&lt;code&gt;-deploy&lt;/code&gt;指定用于创建自包含应用程序的捆绑程序的参数。每种类型的捆绑器都有自己的一组参数。&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;appVersion=version&lt;/code&gt;:应用程序包的版本。一些捆绑器会限制版本字符串的格式。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;classPath=path&lt;/code&gt; :相对于组装的应用程序目录的类路径。javapackager该路径通常从 JAR 文件清单中提取，如果您使用其他命令，则不需要设置。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;icon=path&lt;/code&gt;:用于启动器和其他辅助的默认图标的位置。对于 Windows，格式必须为.ico.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;identifier=value&lt;/code&gt;:用于其他平台特定值的默认值，例如&lt;code&gt;mac.CFBundleIdentifier&lt;/code&gt;. 建议使用反向 DNS 顺序，例如&lt;code&gt;com.example.application.my-application&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;jvmOptions=option&lt;code&gt;:运行应用程序时传递给 JVM 的选项。&lt;/code&gt;java&lt;code&gt;可以使用对命令有效的任何选项。要传递多个选项，请使用该&lt;/code&gt;-B&lt;code&gt;选项的多个实例，如下例所示：&lt;/code&gt;-BjvmOptions=-Xmx128m -BjvmOptions=-Xms128m`&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;jvmProperties=property=value&lt;/code&gt;:运行应用程序时要传递给 VM 的 Java 系统属性。可以使用&lt;code&gt;-D&lt;/code&gt;对命令选项有效的任何属性。&lt;code&gt;java&lt;/code&gt;指定属性名称和属性值。要传递多个属性，请使用该&lt;code&gt;-B&lt;/code&gt;选项的多个实例，如下例所示：&lt;code&gt;-BjvmProperties=apiUserName=示例 -BjvmProperties=apiKey=abcdef1234567890&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;mainJar=filename&lt;/code&gt;:包含应用程序主类的 JAR 文件的名称。&lt;code&gt;javapackager&lt;/code&gt;文件名通常从 JAR 文件清单中提取，如果您使用其他命令，则不需要设置。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;preferencesID=node&lt;/code&gt;:要检查的首选项节点以检查用户可以覆盖的 JVM 选项。指定的节点在运行时作为选项传递给应用程序&lt;code&gt;-Dapp.preferences.id&lt;/code&gt;。此参数与&lt;code&gt;userJVMOptions&lt;/code&gt;参数一起使用。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;runtime=path&lt;/code&gt;:运行时=路径要包含在包中的 JRE 或 JDK 的位置。提供 JDK 或 JRE 的根文件夹的文件路径。要使用系统默认的 JRE，请不要提供路径，如下例所示：&lt;code&gt;-Bruntime=&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;serJvmOptions=option=value&lt;/code&gt;:用户可以覆盖的 JVM 选项。&lt;code&gt;java&lt;/code&gt;可以使用对命令有效的任何选项。指定选项名称和选项的值。要传递多个选项，请使用该&lt;code&gt;-B&lt;/code&gt;选项的多个实例，如下例所示：&lt;code&gt;-BuserJvmOptions=-Xmx=128m -BuserJvmOptions=-Xms=128m&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;Windows EXE Bundler 参数&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;copyright=string&lt;/code&gt;:应用程序的版权字符串。字符串必须是不超过 100 个字符的单行。此参数用于各种 exe 和注册表元数据。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;licenseFile=path&lt;/code&gt;:捆绑商提供或记录的最终用户许可协议 (EULA) 的位置。该路径是相对于打包的应用程序资源的，例如&lt;code&gt;-BlicenseFile=COPYING&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;menuHint=boolean&lt;/code&gt;:指示快捷方式是否安装在开始菜单或开始屏幕上的标志。设置为&lt;code&gt;true&lt;/code&gt;安装快捷方式。默认值为&lt;code&gt;true&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;ortcutHint=boolean&lt;/code&gt;:指示快捷方式是否放置在桌面上的标志。设置为&lt;code&gt;true&lt;/code&gt;向桌面添加快捷方式。默认值为&lt;code&gt;false&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;systemWide=boolean&lt;/code&gt;:指示应用程序是安装在 Program Files 中还是安装在用户主目录中的标准位置的标志。设置为&lt;code&gt;true&lt;/code&gt;在 Program Files 中安装应用程序。设置为&lt;code&gt;false&lt;/code&gt;将应用程序安装在用户的主目录中。默认值为&lt;code&gt;false&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;win.menuGroup=group&lt;/code&gt;:何时安装应用程序的菜单&lt;code&gt;menuHint&lt;/code&gt;组&lt;code&gt;true&lt;/code&gt;。&lt;code&gt;menuHint&lt;/code&gt;当is时，该参数被忽略&lt;code&gt;false&lt;/code&gt;。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;vendor=value&lt;/code&gt;:提供申请的公司、组织或个人。此参数用于各种 exe 和注册表元数据。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;Windows MSI Bundler 参数&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;menuHint=boolean&lt;/code&gt;:指示快捷方式是否安装在开始菜单或开始屏幕上的标志。设置为&lt;code&gt;true&lt;/code&gt;安装快捷方式。默认值为&lt;code&gt;true&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;shortcutHint=boolean&lt;/code&gt;:指示快捷方式是否放置在桌面上的标志。设置为&lt;code&gt;true&lt;/code&gt;向桌面添加快捷方式。默认值为&lt;code&gt;false&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;systemWide=boolean&lt;/code&gt;:指示应用程序是安装在 Program Files 中还是安装在用户主目录中的标准位置的标志。设置为&lt;code&gt;true&lt;/code&gt;在 Program Files 中安装应用程序。设置为&lt;code&gt;false&lt;/code&gt;将应用程序安装在用户的主目录中。默认值为&lt;code&gt;true&lt;/code&gt;.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;win.menuGroup=group&lt;/code&gt;:何时安装应用程序的菜单&lt;code&gt;menuHint&lt;/code&gt;组&lt;code&gt;true&lt;/code&gt;。&lt;code&gt;menuHint&lt;/code&gt;当is时，该参数被忽略&lt;code&gt;false&lt;/code&gt;。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;vendor=value&lt;/code&gt;:提供申请的公司、组织或个人。此参数用于各种 exe 和注册表元数据。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;不推荐使用的选项&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;打包工具不再使用以下选项，如果存在则将其忽略。&lt;/p&gt; &lt;/blockquote&gt; &lt;ol&gt; &lt;li&gt;&lt;code&gt;-runtimeversion version&lt;/code&gt;:所需 JavaFX 运行时的版本。已弃用。&lt;/li&gt; &lt;li&gt;&lt;code&gt;-noembedlauncher&lt;/code&gt;:如果存在，打包程序不会将 JavaFX 启动器类添加到 JAR 文件中。已弃用。&lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;&lt;code&gt;-v&lt;/code&gt;选项可以与任何任务命令一起使用以启用详细输出。&lt;/p&gt; &lt;p&gt;当&lt;code&gt;-srcdir&lt;/code&gt;命令中允许该选项时，它可以多次使用。如果指定了该选项，则将在前面选项&lt;code&gt;-srcfiles&lt;/code&gt;中指定的位置查找参数中命名的文件。&lt;code&gt;srcdir&lt;/code&gt;如果没有&lt;code&gt;-srcdir&lt;/code&gt;前面的 ，则使用执行命令&lt;code&gt;-srcfiles&lt;/code&gt;的目录。&lt;code&gt;javapackager&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;例子&lt;/h2&gt; &lt;h3&gt;使用 -createjar 命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;javapackager -createjar -appclass package.ClassName -srcdir classes -outdir out -outfile outjar -v &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;classes将目录的内容打包为&lt;code&gt;outjar.jar&lt;/code&gt;，将应用程序类设置为&lt;code&gt;package.ClassName&lt;/code&gt;。&lt;/p&gt; &lt;h3&gt;使用 -deploy 命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;javapackager -deploy -outdir outdir -outfile outfile -width 34 -height 43  -name AppName -appclass package.ClassName -v -srcdir compiled &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;为应用程序生成&lt;code&gt;outfile.jnlp&lt;/code&gt;和对应的&lt;code&gt;outfile.html&lt;/code&gt;文件，它由34 x 43 像素启动并具有尺寸。&lt;code&gt;outdir``AppName``package.ClassName&lt;/code&gt;&lt;/p&gt; &lt;h3&gt;使用 -makeall 命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;javapackager -makeall -appclass brickbreaker.Main -name BrickBreaker -width 600 -height 600 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;是否所有打包工作，包括编译&lt;code&gt;createjar&lt;/code&gt;、和&lt;code&gt;deploy&lt;/code&gt;.&lt;/p&gt; &lt;h3&gt;使用 -signjar 命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;javapackager -signJar --outdir dist -keyStore sampleKeystore.jks -storePass **** -alias duke -keypass **** -srcdir dist &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;对目录中的所有 JAR 文件进行签名&lt;code&gt;dist&lt;/code&gt;，附加具有指定别名的证书，&lt;code&gt;keyStore&lt;/code&gt;然后&lt;code&gt;storePass&lt;/code&gt;将签名的 JAR 文件放回&lt;code&gt;dist&lt;/code&gt;目录中。&lt;/p&gt; &lt;h3&gt;将 -deploy 命令与 Bundler 参数一起使用&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;javapackager -deploy -native exe -BsystemWide=true -BjvmOptions=-Xmx128m      -BjvmOptions=-Xms128m -outdir packages -outfile BrickBreaker -srcdir dist      -srcfiles BrickBreaker.jar -appclass brickbreaker.Main -name BrickBreaker      -title &amp;quot;BrickBreaker demo&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;生成本机 Windows EXE 包，用于将 BrickBreaker 应用程序作为自包含应用程序运行。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;javapackager -deploy -native   -outdir packages -outfile md5 -srcfiles  md5.jar -appclass com.sysware.md5.MyFrame -name md5 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;生成本机安装包(在linux下生成deb,在windows下生成exe,在mac下生成dkg)，用于将 md5.jar 应用程序作为自包含应用程序运行。&lt;/p&gt; &lt;p&gt;分析一下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;javapackager---java8自带的打包程序  -deploy---用来构建目标机器的发行版本，简单说就是打包成exe或者其他平台的包，如果不带任何参数，会生成一个基本的应用程序，不建议不带任何参数  -native image---为jar创建磁盘镜像（可以将image替换为其他的类型，例如exe，msi，deb，rpm,dmg,pkg,省略就是该系统支持的文件格式）  -outdir packages---输出目录。  -outfile md5----输出文件（不要带后缀，比如md5.exe，就写md5就行了）。  -srcfiles md5.jar---要打包的jar文件。  -appclass com.sysware.md5.MyFrame---jar文件的主类的全限定名。（注意是全限定名）  -name md5---启动之后的应用名称 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;文章翻译自:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javapackager.html&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Thu, 08 Sep 2022 09:07:00 GMT</pubDate>
    </item>
    <item>
      <title>厨房调料怎么挑?</title>
      <link>https://maruifu.cn/article/247</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;怎么调?其实主要是看配料表,国家严格要求,按照配料东西的比例有大到小写的顺序&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;普及一下&lt;/h2&gt; &lt;p&gt;谷氨酸钠=味精&lt;/p&gt; &lt;p&gt;呈味核=增味剂&lt;/p&gt; &lt;p&gt;苯甲酸钠=防腐剂&lt;/p&gt; &lt;p&gt;山梨酸钾=防腐剂&lt;/p&gt; &lt;p&gt;氯化钠=盐&lt;/p&gt; &lt;p&gt;酵母提取物=味精&lt;/p&gt; &lt;h2&gt;香油怎么选&lt;/h2&gt; &lt;p&gt;工艺:水代的比压榨的好&lt;/p&gt; &lt;p&gt;产地:国产的比埃塞俄比亚就好&lt;/p&gt; &lt;h2&gt;味精鸡精的秘密&lt;/h2&gt; &lt;p&gt;其实都是提鲜的,其实味精是最干净的 用小麦提炼的&lt;/p&gt; &lt;p&gt;鸡精中比例最大的就是味精&lt;/p&gt; &lt;h2&gt;酱油怎么选&lt;/h2&gt; &lt;p&gt;生抽:增鲜提味用的&lt;/p&gt; &lt;p&gt;老抽:菜上色用的&lt;/p&gt; &lt;p&gt;其他功能性的:蒸鱼的,吃海鲜的,草菇,极味鲜,鲜上鲜 其实是生抽的一种,其实没必要买,里面的配料都有谷氨酸钠,呈味核,目的是发酵时间不够,添加出来的味道.买酱油肯定是吃自然发酵的,&lt;/p&gt; &lt;p&gt;配料中有一个脱脂大豆,其实就是炼油厂脱完脂的大豆的豆渣,酱油厂商为了降低成本所以使用这些脱脂大豆,好的酱油都是用全脂大豆,像非转基因大豆,小麦粉酿出来的才是好酱油&lt;/p&gt; &lt;h2&gt;白酒怎么选&lt;/h2&gt; &lt;p&gt;不说那些茅台,五粮液啥的太贵了,不是我们喝的,就说一下性价比最高的高粱酒&lt;/p&gt; &lt;p&gt;执行标准分三类:液态法白酒,固液态法白酒和固态法白酒&lt;/p&gt; &lt;p&gt;液态法白酒:其实也是蒸馏出来的,但是不是用粮食酿出来的,就是红薯类似的东西加上食用酒精和香精勾兑出来的,市面上已经比较少了&lt;/p&gt; &lt;p&gt;固液态法白酒:一部分是纯粮 酿造出来的,另一部分就是用红薯干酿造出来一起兑的,市面上百分之八九十写的纯粮食酿的就是用固液态法&lt;/p&gt; &lt;p&gt;固态法白酒:纯粮食做基础酿造出来的&lt;/p&gt; &lt;p&gt;为什么找纯粮的酒,他醒酒快,口不干,第二天也不难受,所以我们买酒买固态法白酒,&lt;/p&gt; &lt;p&gt;执行编号:G/BT20761 酱香型   G/BT10781.2 清香型  G/BT10781.1 浓香型&lt;/p&gt; &lt;h2&gt;白醋怎么选&lt;/h2&gt; &lt;p&gt;不要选带冰醋精的没啥营养价值,价格便宜,酸度非常酸.也不要选苯甲酸钠的,有这个说明原材料成分很低,水的成分高&lt;/p&gt; &lt;p&gt;白醋最基本的就是用大米或者糯米发酵而成的.用了酒精说明是一种速成不是自然发酵而成的,可以吃但是不是一个营养价值非常好的白醋&lt;/p&gt; &lt;p&gt;白醋适合做热菜,米醋的酸度比较柔和适合做凉菜&lt;/p&gt; &lt;h2&gt;陈醋怎么选&lt;/h2&gt; &lt;p&gt;三个点:酸度,执行标准号,配料&lt;/p&gt; &lt;p&gt;焦糖色:专门用来调颜色的,一般是醋的发酵时间不够,加焦糖色,调一下颜色&lt;/p&gt; &lt;p&gt;苯甲酸钠:防腐剂,因为酸度不够,怕坏了&lt;/p&gt; &lt;p&gt;酸度在5%以上,都是固态发酵&lt;/p&gt; &lt;p&gt;陈醋:gb/t 18187,只不过工艺不一样,用高粱发酵的酸度高一些,糯米的酸中有一些回甜&lt;/p&gt; &lt;p&gt;山西老陈醋:执行标准必须是gb/t 19777.第二配料一定是高粱,用高粱发酵的&lt;/p&gt; &lt;p&gt;镇江香醋:gb/t 18623,酸度柔和,第二配料一定是糯米,用糯米发酵的&lt;/p&gt; &lt;p&gt;一般炒菜用陈醋,拌凉菜用香醋&lt;/p&gt; &lt;h2&gt;盐怎么选&lt;/h2&gt; &lt;p&gt;背景:因为七八十年代大部分人缺碘,出现了大脖子病,1995年国家要求全民吃碘盐最近这几年得甲亢的或者高碘区的人,他们摄入的碘已经够了,没有卖不加碘的盐,2017年国家又放开了政策出现了海藻盐,不含碘盐,低钠盐,低钠海藻盐,还有岩盐.&lt;/p&gt; &lt;p&gt;不加碘的盐:如果你经常吃海带,海藻,海鱼,海虾等海产品比较多或者你有甲亢.其实你本身 碘的补充是多了,这时候不含碘的盐才适合你吃.&lt;/p&gt; &lt;p&gt;低钠盐:高血压,心脏病呀这类患者适合吃低钠盐,普通盐里面氯化钠含量很高,会促使血管膨胀,血流加快&lt;/p&gt; &lt;p&gt;岩盐:比较高端的一种盐,也叫喜马拉雅盐,矿物质含量比较多,一般在高档餐厅,高档烤肉或者一些料理餐厅中,一般炒菜用不到这种,有钱人随意哈,&lt;/p&gt; &lt;h2&gt;耗油怎么选&lt;/h2&gt; &lt;p&gt;谷氨酸钠(味精),呈味核(味精精) 为啥要加这些因为耗的成分少,我们是要吃用耗提炼出来的精华去做菜,让味道更鲜美,不是吃水掺了的酱油加了一些耗油的耗油汁,&lt;/p&gt; &lt;p&gt;山梨酸钾,苯甲酸钠 其实就是防腐剂,因为水的比例比较多,容易坏所以加这些防腐剂&lt;/p&gt; &lt;p&gt;一般配料表中第一位是耗汁的才是好耗油,没有防腐剂的当然没有味精焦糖色更好,不过市面上很少见没有添加味精的.&lt;/p&gt; &lt;h2&gt;食用油怎么选&lt;/h2&gt; &lt;h3&gt;低档油&lt;/h3&gt; &lt;p&gt;调和油:看配料表  现在比较争议比较大的 转基因的好不好 这个我也不知道,但是非转基因的肯定没问题,&lt;/p&gt; &lt;p&gt;调和油没有加工工艺&lt;/p&gt; &lt;h3&gt;中档油&lt;/h3&gt; &lt;p&gt;食用油的三种加工工艺:浸出,热压(热榨),冷压(冷榨)&lt;/p&gt; &lt;p&gt;浸出:用化学试剂去浸泡,再去压榨,再去脱酸脱臭的一些处理,里面有一些试剂的残留,不过国家允许范围内,这种不太健康,不过便宜 因为出油率高.&lt;/p&gt; &lt;p&gt;热榨:温度在60以上,相对冷榨出油率高,所以性价比好一些,没必要买冷榨的除了贵 ,没太多优点,营养价值比不上高档油&lt;/p&gt; &lt;p&gt;还有一个问题一级,S级,浓香,特级和油的品质一毛钱关系都没有不用关心这个&lt;/p&gt; &lt;p&gt;油我们除了吃一个口味外还有一个营养价值饱和脂肪酸的含量和不饱和脂肪酸的含量&lt;/p&gt; &lt;p&gt;饱和脂肪酸尽量少吃,容易三高,脂肪含量高,各方面数据高,不饱和脂肪酸高可以防止三高这些东西&lt;/p&gt; &lt;p&gt;不饱和脂肪酸有单不饱和脂肪酸和多不饱和脂肪酸&lt;/p&gt; &lt;p&gt;中档油中不同油脂单不饱和脂肪酸的含量从高到低&lt;/p&gt; &lt;p&gt;橄榄油&amp;gt;油茶籽油&amp;gt;一般菜籽油&amp;gt;低芥酸菜籽油&amp;gt;花生油&amp;gt;米糠油&amp;gt;芝麻油&amp;gt;棕榈油&amp;gt;玉米油&amp;gt;花椒籽油&amp;gt;葵花籽油&amp;gt;亚麻籽油&amp;gt;鸡油&amp;gt;大豆油&amp;gt;葡萄籽油&amp;gt;鸭油&amp;gt;核桃油&amp;gt;红花籽油&amp;gt;羊油&amp;gt;棉籽油&amp;gt;牛油&amp;gt;猪油&lt;/p&gt; &lt;p&gt;中档油中不同油脂多不饱和脂肪酸的含量从高到低&lt;/p&gt; &lt;p&gt;牛油&amp;gt;羊油&amp;gt;猪油&amp;gt;鸭油&amp;gt;鸡油&amp;gt;棕榈油&amp;gt;棉籽油&amp;gt;花生油&amp;gt;橄榄油&amp;gt;米糠油&amp;gt;玉米油&amp;gt;芝麻油&amp;gt;大豆油&amp;gt;葡萄籽油&amp;gt;花椒籽油&amp;gt;葵花籽油&amp;gt;核桃油&amp;gt;亚麻籽油&amp;gt;一般菜籽油&amp;gt;红花籽油&amp;gt;低芥酸籽油&amp;gt;油茶籽油&lt;/p&gt; &lt;p&gt;葵花籽油和菜籽油这两个混着吃,营养要均衡比如这周吃单不饱和脂肪酸含量最多的 下周吃 多不饱和脂肪酸含量最多的&lt;/p&gt; &lt;h3&gt;高档油&lt;/h3&gt; &lt;p&gt;橄榄油,亚麻籽油,核桃油,山茶油都说自己是世界上最好的油&lt;/p&gt; &lt;p&gt;高档油除了看配料外另外就是能量表&lt;/p&gt; &lt;p&gt;橄榄油和山茶油 是单不饱和脂肪酸特别高&lt;/p&gt; &lt;p&gt;亚麻籽油和核桃油,是多不饱和脂肪酸特别高&lt;/p&gt; &lt;p&gt;橄榄油:看配料 精炼的(是把特级和一级混合到一起了)没有特级的好,橄榄油特点炒菜没有味 不适合中餐&lt;/p&gt; &lt;p&gt;山茶油:煎炒烹炸炖都合适 ,单不饱和脂肪酸推荐这个&lt;/p&gt; &lt;p&gt;亚麻籽油和核桃油:能量是一样的,推荐亚麻籽油 因为他便宜五六十一斤 ,核桃油一百五六一斤,这种两油有个问题不耐高温,炒炸不适合,适合拌凉菜啥的&lt;/p&gt; &lt;p&gt;山茶油炒菜吃,亚麻籽油拌凉菜吃&lt;/p&gt; &lt;h2&gt;番茄沙司怎么选&lt;/h2&gt; &lt;p&gt;食品添加剂多的不要买&lt;/p&gt; &lt;p&gt;冰乙酸 瓜尔胶 柠檬酸 苯甲酸钠 黄原胶 有这写配料的 番茄纯度不高 水的比例高,需要加一些色素,胶增稠.&lt;/p&gt; &lt;p&gt;番茄沙司就是调制的都是兑的,番茄酱是西红柿打成的酱&lt;/p&gt; &lt;p&gt;有辣椒粉(增红) 和淀粉添加剂(增稠)的 也可以购买吧&lt;/p&gt; &lt;p&gt;买就最好买那种无添加剂无淀粉辣椒粉的那种是最好的&lt;/p&gt; &lt;h2&gt;牛奶怎么选&lt;/h2&gt; &lt;p&gt;大致分六类&lt;/p&gt; &lt;p&gt;纯牛奶:营养成分保留最全面的,鲜牛奶高温杀菌而成,分两种巴氏奶和常温奶,巴氏奶保质期短一般最多只有21天,常温奶有6个月不过处理的更复杂,杀菌更多,当然营养成分也会流失一些,纯牛奶配料很简单就一个生牛乳,推荐和巴氏奶,营养和口感都比常温的要好&lt;/p&gt; &lt;p&gt;舒化奶:也就是无乳糖牛奶,喝牛奶拉肚子,是对常规的牛奶的乳糖,消化系统不接受这种糖.喝常规奶拉肚子就需要喝这种无乳糖样的奶,里面有乳糖酶会把乳糖给水解掉.但是口感喝起来很甜, 营养成分和纯牛奶差不多,选择时候选配料比较干净的就生牛乳和乳糖酶的就行.&lt;/p&gt; &lt;p&gt;高钙奶:比普通的奶百毫升中多了25毫克钙,里面添加的是碳钙和乳酸钙,这种钙很难吸收,并不比纯牛奶的营养高,平常经常吃奶制品或乳制品或豆制品,没必要买这种奶&lt;/p&gt; &lt;p&gt;脱脂牛奶:常规奶都是全脂牛奶,脂肪比例在3%,脱脂牛奶通过离心技术把脂肪含量控制在0.5%,但是这种牛奶口味淡,没有那种醇香奶香的味道,和兑了水似的.脱脂过程中,其实维生素ADE也没有了,配料也是只有生牛乳,&lt;/p&gt; &lt;p&gt;早餐奶:和早餐没啥关系,有大量的添加剂,只不过里面含有奶的成分,其实就是含乳饮料,不推荐这种&lt;/p&gt; &lt;p&gt;含乳饮料:优酸乳一类的 不推荐&lt;/p&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;蚝油:推荐财神蚝油 和 旧庄蚝油(更好)&lt;/p&gt; &lt;p&gt;酱油:丸庄 黑豆原汁酱油 不是原酿酱油,千禾 380天零添加酱油不是180天的(有脱脂大豆)&lt;/p&gt; &lt;p&gt;香醋:镇江恒顺香醋&lt;/p&gt; &lt;p&gt;陈醋:千禾 3年窖藏 还有山西老陈醋 国标GB/T19777不是山西陈醋&lt;/p&gt; &lt;p&gt;白醋:东湖九度白醋和千禾糯米白醋 还有鲁花糯米白醋 选7度以上的 酸度才够 国标号都是GB/T18187&lt;/p&gt; &lt;p&gt;料酒:欣和有机米香料酒 ,千禾零添加烹调料酒&lt;/p&gt; &lt;p&gt;食用油:炒菜油推荐菜籽油和葵花籽油,高档点的推荐山茶油&lt;/p&gt; &lt;h2&gt;常见食品添加剂&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;分类&lt;/th&gt;&lt;th&gt;食品添加剂&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;防腐剂&lt;/td&gt;&lt;td&gt;苯甲酸及其盐类、山梨酸及其盐类、亚硫酸及其盐类、化碳、亚硝酸盐类等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;抗氧化剂&lt;/td&gt;&lt;td&gt;茶多酚、生育酚、黄酮类、丁基羟基茴香醚等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;着色剂&lt;/td&gt;&lt;td&gt;甜菜红、姜黄、红曲红、胭脂虫红、苋菜红、胭脂红、日落黄、柠檬黄、新红、诱惑红、赤藓红、亮蓝、靛蓝等，多用于果汁饮料等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;护色剂&lt;/td&gt;&lt;td&gt;亚硝酸钠、亚硝酸钾、硝酸钠、硝酸钾(绿色食品中禁止使用)，比如加工肉类。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;甜味剂&lt;/td&gt;&lt;td&gt;糖精、糖精钠、甜蜜素、安赛蜜、三氯蔗糖、阿斯巴甜，常见于一些甜味的加工食品中，比如饮料、面包等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;增稠剂&lt;/td&gt;&lt;td&gt;阿拉伯胶、卡拉胶、果胶、琼胶、海藻酸类、甲壳素、瓜尔胶、黄原胶、明胶、干酪素、壳聚糖、羧甲基纤维素钠，比如酸奶等一些黏稠的食品中。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;凝固剂&lt;/td&gt;&lt;td&gt;硫酸钙、氯化钙、氯化镁、丙二醇主要用于豆制品生产和果蔬深加工，以及凝胶食品的制造。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;膨松剂&lt;/td&gt;&lt;td&gt;碳酸氢钠、碳酸氢铵、硫酸铝钾，膨松剂主要用于饼干、糕点生产。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;水分保持剂&lt;/td&gt;&lt;td&gt;乳化盐、甘油、丙二醇、麦芽糖精、山梨糖醇等广泛地用于食品加工，肉类制品中保持肉的持水性增加肉的柔嫩性，防止啤酒和饮料等浑浊。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Wed, 07 Sep 2022 14:17:00 GMT</pubDate>
    </item>
    <item>
      <title>mysql中的查询计划及sql语句性能分析</title>
      <link>https://maruifu.cn/article/246</link>
      <content:encoded>&lt;h2&gt;准备测试数据&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;CREATE TABLE `employee` (   `id` int NOT NULL AUTO_INCREMENT,   `name` varchar(20) DEFAULT NULL,   `dep_id` int DEFAULT NULL,   `age` int DEFAULT NULL,   `salary` decimal(10,2) DEFAULT NULL,   `cus_id` int DEFAULT NULL,   PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB  AUTO_INCREMENT=1  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;  CREATE TABLE `customer` (   `id` int NOT NULL,   `name` varchar(255) DEFAULT NULL,   PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB  AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;  CREATE TABLE `department` (   `id` int NOT NULL AUTO_INCREMENT,   `deptName` varchar(30) DEFAULT NULL,   `address` varchar(40) DEFAULT NULL,   PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;     -- 测试数据 INSERT INTO employee VALUES ( 1, '鲁班', 1, 10, 1000.00, 1 ); INSERT INTO employee VALUES ( 2, '后裔', 1, 20, 2000.00, 1 ); INSERT INTO employee VALUES ( 3, '孙尚香', 1, 20, 2500.00, 1 ); INSERT INTO employee VALUES ( 4, '凯', 4, 20, 3000.00, 1 ); INSERT INTO employee VALUES ( 5, '典韦', 4, 40, 3500.00, 2 ); INSERT INTO employee VALUES ( 6, '貂蝉', 6, 20, 5000.00, 1 ); INSERT INTO employee VALUES ( 7, '孙膑', 6, 50, 5000.00, 1 ); INSERT INTO employee VALUES ( 8, '蔡文姬', 30, 35, 4000.00, 1 );   -- 测试数据  INSERT INTO department VALUES (1, '研发部(RD)', '2层');  INSERT INTO department VALUES (2, '人事部(HR)', '3层');  INSERT INTO department VALUES (3, '市场部(MK)', '4层');  INSERT INTO department VALUES (4, '后勤部(MIS)', '5层');  INSERT INTO department VALUES (5, '财务部(FD)', '6层');   -- 测试数据  INSERT INTO customer VALUES (1, 'zs');  INSERT INTO customer VALUES (2, 'lisi');  INSERT INTO customer VALUES (3, 'wangwu'); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;explain的简介&lt;/h2&gt; &lt;p&gt;mysql中可以使用&lt;strong&gt;explain&lt;/strong&gt;这个关键字来获取（查询）sql语句的&lt;strong&gt;查询执行计划&lt;/strong&gt;的。&lt;strong&gt;使用explain关键字，可以模拟mysql优化器执行的sql语句，从而知道mysql是如何处理sql语句的&lt;/strong&gt;。&lt;strong&gt;通过explain可以分析查询语句或表结构的性能瓶颈&lt;/strong&gt;。&lt;/p&gt; &lt;h2&gt;explain的作用&lt;/h2&gt; &lt;p&gt;①、查看表的读取顺序&lt;/p&gt; &lt;p&gt;②、数据读取操作的操作类型&lt;/p&gt; &lt;p&gt;③、查看哪些索引可以使用&lt;/p&gt; &lt;p&gt;④、查看哪些索引被实际使用&lt;/p&gt; &lt;p&gt;⑤、查看表之间的引用&lt;/p&gt; &lt;p&gt;⑥、查看每张表有多少行被优化器执行&lt;/p&gt; &lt;h2&gt;explain的使用方法&lt;/h2&gt; &lt;p&gt;explain sql语句&lt;/p&gt; &lt;pre&gt;&lt;code&gt;explain select * from employee; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;explain执行计划输出中的各个列的详解&lt;/h2&gt; &lt;h3&gt;id&lt;/h3&gt; &lt;p&gt;描述：select查询的序列号,包含一组数字，该组数字表示查询中执行select子句或操作表的顺序&lt;/p&gt; &lt;p&gt;&lt;strong&gt;id值的三种情况如下：&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;id相同&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;-- 分析的sql语句 explain select * from employee e,department d,customer c where e.dep_id = d.id and e.cus_id = c.id; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;分析的结果截图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822102355143.png" alt="image-20220822102355143" title="image-20220822102355143" /&gt;&lt;/p&gt; &lt;p&gt;从上图中可以看到，id列的值都是1。那么该条sql语句的&lt;strong&gt;执行顺序是由上到下&lt;/strong&gt;，也就是说 先查询的c表 然后查询 e表 最后查询d表。&lt;/p&gt; &lt;h4&gt;id不同&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;-- 分析的sql语句 EXPLAIN SELECT * from department  WHERE id = (SELECT id from employee WHERE id= (SELECT id from customer WHERE id = 1) ) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822102659335.png" alt="image-20220822102659335" title="image-20220822102659335" /&gt;&lt;/p&gt; &lt;p&gt;从上图中可以看到，id列的值是1、2、3。那么该条sql语句的执行顺序是从大到小（由下到上），也就是说 id列的值是3的先执行 其次是id列的值是2 最后是id列的值是1再执行。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;这里都是子查询，如果是子查询，id的序号会递增，id值越大优先级越高，优先被执行。**&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;id相同和不同&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;-- 分析的sql语句 EXPLAIN  SELECT * FROM department d, ( SELECT * FROM employee GROUP BY dep_id ) t WHERE d.id = t.dep_id; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822103446370.png" alt="image-20220822103446370" title="image-20220822103446370" /&gt;&lt;/p&gt; &lt;p&gt;从上图中可以看到，id列的值是1、1、2。那么该条sql语句的执行顺序是怎样的呢？根据上面的①和②这里应该也能猜到了。该条sql语句的执行顺序是 先执行id列的值是2的，其次执行id列的值是1的（最上面那个id列的值是1的，也就是table列的值是d），最后执行中间那个id列的值是1的。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;上图中有一个select_type列，其中select_type有一列的值是derived，而derived表示 衍生出来的虚表。再次说明，&lt;strong&gt;id值越大，优先级越高，越先执行&lt;/strong&gt;。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;总结&lt;/h4&gt; &lt;p&gt;相同，顺序走（由上到下）,不同，看谁大，大的先执行。&lt;/p&gt; &lt;h3&gt;select_type&lt;/h3&gt; &lt;p&gt;**描述：**查询类型，主要用于区别普通查询，联合查询，子查询等复杂查询。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;select_type列的值主要有以下6种情况&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;①、SIMPLE：简单的select查询，查询中不包含子查询或者UNION&lt;/p&gt; &lt;p&gt;②、PRIMARY：查询中若包含任何复杂的子查询，那么最外层的查询则被标记为primary&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573652255813820.png" alt="m4.png" title="m4.png" /&gt;&lt;/p&gt; &lt;p&gt;③、SUBQUERY：在select或where中包含了子查询&lt;/p&gt; &lt;p&gt;④、DERIVED：在from列表中包含的子查询被标记为derived(衍生)，把结果放在临时表当中。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573652751243003.png" alt="m5.png" title="m5.png" /&gt;&lt;/p&gt; &lt;p&gt;⑤、UNION：若第二个select出现在union之后，则被标记为union。若union包含在from子句的子查询中，外层select将被标记为deriver。&lt;/p&gt; &lt;p&gt;⑥、UNION RESULT：从union表获取结果select。两个UNION合并的结果集在最后。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573653214834456.png" alt="m6.png" title="m6.png" /&gt;&lt;/p&gt; &lt;h3&gt;table&lt;/h3&gt; &lt;p&gt;**描述：**显示当前查询的数据是关于哪张表的。&lt;/p&gt; &lt;h3&gt;partitions&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;描述&lt;/strong&gt;：如果查询是基于分区表的话，会显示查询访问的分区。&lt;/p&gt; &lt;h3&gt;type（重要）&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;描述&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;表示访问某个表的类型。更专业一点的解释就是：type代表着mysql对某个表的执行查询时的访问方法，其中type列的值就表明了这个访问方法是个啥。通过type可以知道mysql是做了全表扫描还是范围扫描等，从而知道当前的sql语句到底要不要去优化。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;type列的值一般最常用的有7种，按照最好到最差来排序 分别是：system&amp;gt;const&amp;gt;eq_ref&amp;gt;ref&amp;gt;range&amp;gt;index&amp;gt;ALL。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;system&lt;/h4&gt; &lt;p&gt;表中只有一条记录，并且该表使用的存储引擎的统计数据是精确的，比如MyISAM、Memory，那么该表的type列的值就是system。这是const类型的特例，平时不会出现，也不用奢求将sql优化到这种级别的。&lt;/p&gt; &lt;h4&gt;const&lt;/h4&gt; &lt;p&gt;表示通过索引（主键索引或唯一索引）一次就找到了那一条数据。这里和上面那个system的区别就是 system表里面只能有一条数据，而const表示表中可能会有多条数据，但是const能直接从多条数据中直接定位到那一条数据（通过主键索引或唯一索引）。因为只匹配一行数据，所以const速度很快。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573656145168644.png" alt="m8.png" title="m8.png" /&gt;&lt;/p&gt; &lt;h4&gt;eq_ref&lt;/h4&gt; &lt;p&gt;唯一性索引扫描。对于每个索引键，表中只有一条记录与之匹配。常见于主键或唯一索引扫描。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573658486930872.png" alt="m9.png" title="m9.png" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573658493106475.png" alt="m10.png" title="m10.png" /&gt;&lt;/p&gt; &lt;p&gt;employee表中有五条数据，而department表中有对应的五条数据，其中employee的id（主键索引）和department的id（主键索引）是一 一对应的，所以这里就会出现eq_ref，eq_ref也就是这个意思。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;eq_ref基本上很难在单表上出现，一般都是在多表的情况下才会出现eq_ref。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;ref&lt;/h4&gt; &lt;p&gt;非唯一性索引扫描。大白话解释一下就是：出现该连接类型的条件是， 查找条件列使用了索引而且不是使用的主键索引和唯一索引（unique），使用的是普通索引。其实，意思就是虽然使用了索引，但该索引列的值并不唯一，有重复。这样即使 使用索引快速查找到了第一条数据，仍然不能停止扫描，要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表，因为索引是有序的，即便有重复值，也是在一个非常小的范围内扫描。下面为了演示这种情形，给employee表中的age列添加一个普通的索引&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ALTER TABLE employee ADD INDEX idx_age(age) USING BTREE; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822112919326.png" alt="image-20220822112919326" title="image-20220822112919326" /&gt;&lt;/p&gt; &lt;h4&gt;range&lt;/h4&gt; &lt;p&gt;指的是有范围的索引扫描，相对于index的全索引扫描，它有范围限制，因此要优于index。关于range比较容易理解，需要记住的是出现了range，则一定是基于索引的。一般就是在你的where语句中出现between，and，&amp;lt;，&amp;gt;，or，in等查询，那么type列的值就是range&lt;/p&gt; &lt;h4&gt;index&lt;/h4&gt; &lt;p&gt;Full Index Scan。index与All区别为index类型只遍历索引树，通常比All要快，因为索引文件通常比数据文件要小。all和index都是读全表，但index是从索引中读取，all是从硬盘当中读取。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822113132241.png" alt="image-20220822113132241" title="image-20220822113132241" /&gt;&lt;/p&gt; &lt;h4&gt;ALL&lt;/h4&gt; &lt;p&gt;将表中的所有数据进行了扫描（全表扫描），从硬盘当中读取数据。如果出现了All 且数据量非常大，那么该条sql必须去做优化的。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573654605121973.png" alt="m7.png" title="m7.png" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;说下要求：一般来说，要保证SQL查询至少达到range级别，最好能达到ref级别。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;possible_keys&lt;/h3&gt; &lt;p&gt;**描述：**表示这张表中可能会用到的索引（一个或多个），查询涉及到的字段上若存在索引，则该索引将被列出，但不一定被查询实际使用到，可能自己创建了4个索引，在实际执行sql查询的时候，根据mysql内部的自动判断，只使用了3个。&lt;/p&gt; &lt;h3&gt;key（重要）&lt;/h3&gt; &lt;p&gt;**描述：**mysql在执行的时候实际使用到的索引，如果为NULL，则没有使用索引。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;其它说明：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;查询中若使用了覆盖索引，则该索引仅出现在key列表中。&lt;/p&gt; &lt;p&gt;覆盖索引：查询的字段和建立的字段刚好吻合，这种我们称为覆盖索引。&lt;/p&gt; &lt;p&gt;possible_keys与key关系：前者表示理论应该用到哪些索引，后者表示实际用到了哪些索引。&lt;/p&gt; &lt;h3&gt;key_len&lt;/h3&gt; &lt;p&gt;**描述：**表示索引中使用的字节数，可通过该列计算查询中使用的索引长度 。下面为了演示这种情形，给employee表添加一个复合索引。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ALTER TABLE employee ADD INDEX idx_name_dep_id_age(name, dep_id, age) USING BTREE; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822113440503.png" alt="image-20220822113440503" title="image-20220822113440503" /&gt;&lt;/p&gt; &lt;h3&gt;ref&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;描述：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;索引是否被引入到，到底引用到了哪几个索引。&lt;/p&gt; &lt;p&gt;这里就不写加索引的语句了，直接上几张截图看吧&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822113608949.png" alt="image-20220822113608949" title="image-20220822113608949" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822113723594.png" alt="image-20220822113723594" title="image-20220822113723594" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573696234904324.png" alt="m6.png" title="m6.png" /&gt;&lt;/p&gt; &lt;h3&gt;rows&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;描述：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;根据表的统计信息及索引选用情况，大致估算出找到所需的记录所需要扫描（读取）的行数。表有多少行被优化器查询过。没有建立索引和建立索引之后 rows所显示的数据肯定是不一样的。这里就不进行截图演示了。&lt;/p&gt; &lt;h3&gt;filtered&lt;/h3&gt; &lt;p&gt;**描述：**满足查询的记录数量的比例，注意是百分比，不是具体记录数，值越大越好，filtered列的值依赖统计信息，并不十分准确。对于单表查询来说，这个filtered列的值没什么意义，更关注在连接查询中对应的执行计划记录的filtered列的值。关于这里的多表demo也就先不演示了。&lt;/p&gt; &lt;h3&gt;Extra&lt;/h3&gt; &lt;p&gt;**描述：**顾名思义，Extra列是用来说明一些额外信息的，可以通过这些额外信息来更准确的理解mysql到底将如何执行给定的查询语句。mysql提供的额外信息有好几十个，这里就不一个一个介绍了，只挑一些平时常见的或者比较重要的做下说明。&lt;/p&gt; &lt;p&gt;①、Using filesort：专业术语成为“文件排序”。说明mysql会对数据使用一个外部的索引排序，而不是按照表内的索引顺序进行。mysql无法利用索引完成排序操作称为&amp;quot;文件排序&amp;quot;，当你看到using filesort的时候，那么一定要优化该条sql语句。（得到所需结果集，需要对所有记录进行&amp;quot;文件排序&amp;quot; 出现这个 表示该条SQL语句性能较低，需要进行优化）&lt;/p&gt; &lt;p&gt;**关于filesort的更多详解：**filesort 并不是说通过磁盘文件进行排序，而只是告诉我们进行了一个排序操作。文件排序是通过相应的排序算法，将取得的数据在内存中进行排序。mysql需要将数据在内存中进行排序，所使用的内存区域也就是我们通过 &lt;strong&gt;sort_buffer_size&lt;/strong&gt; 系统变量所设置的排序区。这个排序区是每个 Thread 独享的，所以说可能在同一时刻 在mysql中可能存在多个 sort buffer 内存区域。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;filesort分两种：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;双路排序：是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行指针信息，然后在sort buffer 中进行排序。排序后再吧查询字段依照行指针取出，共执行两次磁盘io。&lt;/p&gt; &lt;p&gt;单路排序：是一次性取出满足条件行的所有字段，然后在sort buffer中进行排序。 执行一次磁盘io。&lt;/p&gt; &lt;p&gt;在mysql4.1版本之前只有第一种排序算法 双路排序。第二种算法是从mysql4.1开始的改进算法，主要目的是为了减少第一次算法中需要两次访问表数据的 IO 操作，将两次变成了一次，但相应也会耗用更多的sort buffer 空间。当然，mysql4.1开始的以后所有版本同时也支持第一种算法。&lt;/p&gt; &lt;p&gt;典型说明：在一个没有建立索引的列上进行了order by，就会触发filesort，常见的优化方案是，在order by的列上添加索引，避免每次查询都全量排序。&lt;/p&gt; &lt;p&gt;Using filesort示例截图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573697453907675.png" alt="m7.png" title="m7.png" /&gt;&lt;/p&gt; &lt;p&gt;②、Using temporary：在许多查询的执行过程中，mysql可能会借助临时表来完成一些功能，比如去重、排序之类的，比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中，如果不能有效利用索引来完成查询，mysql很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表，在执行计划的Extra列将会显示using temporary提示。当你看到using temporary的时候，那么一定要优化该条sql语句。（需要建立临时表(temporary table)来暂存中间结果，出现这个 表示该条SQL语句性能较低，通常情况下需要进行优化）&lt;/p&gt; &lt;p&gt;③、Useing index：表示相应的select中使用了覆盖索引，避免访问了表中的数据行，效率很好。如果同时出现了Using where 表明索引被用来执行索引键值的查找。如果没有同时出现Using where 表明索引 用来读取数据而非执行查找动作。（SQL所需要返回的所有列数据均在一棵索引树上，而无需访问实际的行记录，出现这个 表示该条SQL语句性能较好）&lt;/p&gt; &lt;p&gt;示例截图：&lt;/p&gt; &lt;p&gt;using index示例截图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573698971307451.png" alt="m9.png" title="m9.png" /&gt;&lt;/p&gt; &lt;p&gt;using where using index示例截图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/image-20220822113825870.png" alt="image-20220822113825870" title="image-20220822113825870" /&gt;&lt;/p&gt; &lt;p&gt;④、Using where：说明使用了where过滤（SQL使用了where条件过滤数据 需要需要优化该条SQL语句 需要配合explain结果中的type（连接类型）来综合判断）&lt;/p&gt; &lt;p&gt;⑤、Using join buffer(Block Nested Loop)：在连接查询执行过程中，当sql查询语句不能有效的利用索引加快访问速度，mysql选择退而求其次，一般会为其分配一块名叫join buffer的内存块来加快查询速度，也就是我们所讲的基于块的嵌套循环算法。（需要进行嵌套循环计算 出现这个 表示该条SQL语句性能较低，需要进行优化）&lt;/p&gt; &lt;p&gt;打个比方：内层和外层的type均为ALL，rows均为4，需要循环进行4*4次计算。&lt;/p&gt; &lt;p&gt;典型说明：两个关联表join，关联字段均未建立索引，就会出现这种情况。常见的优化方案是，在关联字段上添加索引，避免每次嵌套循环计算。&lt;/p&gt; &lt;p&gt;⑥、impossible where：where子句中的值总是false 获取不到任何数据。出现这种提示通常情况下说明你的sql语句有误，请看情况选择是否进行修改相应的sql语句。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/08/22/1573699219459415.png" alt="m10.png" title="m10.png" /&gt;&lt;/p&gt; &lt;p&gt;⑦、Using index condition：确实命中了索引，但不是所有的列数据都在索引树上，还需要访问实际的行记录。（出现这个 表示 该条SQL语句性能也较高，但不如Using index）&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 22 Aug 2022 03:39:24 GMT</pubDate>
    </item>
    <item>
      <title>MySQL方法GROUP_CONCAT的应用</title>
      <link>https://maruifu.cn/article/245</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;MySQL方法GROUP_CONCAT的应用，多对多联表查询，以A表为主表，通过关联表C查询出B表关联A表任意记录的多条记录的某个字段的合并值&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;开发中遇到这样的一个需求：用户表为A，角色表为B，用户角色关系通过C表多对多关联，我们需要查询出每一个用户所拥有的角色，以下图的格式显示：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;用户ID&lt;/th&gt;&lt;th&gt;用户姓名&lt;/th&gt;&lt;th&gt;拥有角色&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;小明1&lt;/td&gt;&lt;td&gt;角色1,角色4,角色5,角色6...&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;小明2&lt;/td&gt;&lt;td&gt;角色1,角色4,角色5,角色6...&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt;-- 用户表 CREATE TABLE `sys_user` (   `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',   `user_name` varchar(50) NOT NULL COMMENT '用户姓名',   `user_age` varchar(3) DEFAULT NULL COMMENT '用户年龄',   `creater` varchar(255) DEFAULT NULL COMMENT '创建人',   `create_time` datetime DEFAULT NULL COMMENT '创建时间',   `updater` varchar(50) DEFAULT NULL COMMENT '更新人',   `update_time` datetime DEFAULT NULL COMMENT '更新时间',   PRIMARY KEY (`user_id`) )  ENGINE=InnoDB AUTO_INCREMENT=1  COMMENT='用户表';  -- 角色表 CREATE TABLE  `sys_role`  (   `role_id` int NOT NULL AUTO_INCREMENT COMMENT '角色ID',   `role_name` varchar(50) NOT NULL COMMENT '角色名称',   `creater` varchar(50) NULL COMMENT '创建人',   `create_time` datetime NULL COMMENT '创建时间',  `updater` varchar(50) NULL COMMENT '更新人',   `update_time` datetime NULL COMMENT '更新时间',   PRIMARY KEY (`role_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1    COMMENT='角色表';  -- 用户角色关系表 CREATE TABLE  `sys_user_role`  (   `user_role_id` int NOT NULL AUTO_INCREMENT COMMENT '用户角色关系ID',   `role_id` varchar(50) NOT NULL COMMENT '角色ID',  `user_id` varchar(50) NOT NULL COMMENT '用户ID',   PRIMARY KEY (`user_role_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1   COMMENT='用户角色关系表';  -- 插入模拟数据 start create procedure insert8() begin declare i int default 0;  repeat  INSERT INTO sys_role(role_name,creater,create_time,updater,update_time)select role_name,   creater, create_time, updater,update_time from sys_role UNION ALL select '角色','1',NOW(),'1', NOW() from dual ; INSERT INTO sys_user(user_name,user_age,creater,create_time,updater,update_time) select  user_name,user_age,creater,create_time, updater,update_time from sys_user UNION ALL select '小明','10','1',NOW(),'1',NOW() from dual ; set i=i+1; until i&amp;gt;8 end repeat;  end; call insert8;  drop procedure if exists insert8;    INSERT INTO  sys_user_role (role_id, user_id )  select   role_id, user_id from sys_role INNER JOIN sys_user ;  update sys_user set user_name=REPLACE(user_name,user_name,CONCAT( user_name,user_id) ); update sys_role set role_name=REPLACE(role_name,role_name,CONCAT(role_name,role_id) ); -- 插入模拟数据 end  -- 测试完毕删除数据 drop table sys_user; drop table sys_role; drop table sys_user_role; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在解决问题中发现了两种方式可实现该功能&lt;/p&gt; &lt;h2&gt;方式一&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; SELECT su.user_id AS userId,su.user_name AS userName,     (         SELECT  GROUP_CONCAT(sr.role_name)         FROM sys_user_role sur         LEFT JOIN sys_role sr ON sr.role_id = sur.role_id         WHERE sur.user_id = su.user_id     ) AS roleNames FROM sys_user su    ORDER BY  su.user_id &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;方式一是把查询c表作为主表，并且left join B表的一个子查询，查出每一个用户id拥有的角色名称拼接结果作为拥有角色字段值的，我们看看其查询性能&lt;/p&gt; &lt;pre&gt;&lt;code&gt;查询时间：56.088s   共511条 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可见查询22条左右数据需要4秒多，这种速度我们显然是不能接受的，而且需要以拥有小区的名称做模糊查询时候也无从下手。于是后来继续想办法优化，就找到了下面的方式二。&lt;/p&gt; &lt;h2&gt;方式二&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;SELECT su.user_id AS userId, su.user_name AS userName,temp.roleNames FROM sys_user su LEFT JOIN (     SELECT sur.user_id, GROUP_CONCAT(sur.role_id) AS roleIds,         GROUP_CONCAT(sr.role_name) AS roleNames     FROM sys_user_role sur      LEFT JOIN sys_role sr ON sr.role_id = sur.role_id     GROUP BY sur.user_id ) temp ON temp.user_id = su.user_id ORDER BY  su.user_id &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;方式二依然有一个查询用户拥有小区名称拼接结果的子查询，只是这个子查询不是直接作为结果字段返回，而是根据用户id为group规则查询出来每一个用户的拥有小区结果字符串，然后作为A表的left join的虚拟表，下面看一下测试结果&lt;/p&gt; &lt;pre&gt;&lt;code&gt;查询时间：0.657s   共511条 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可见同样查询一万条数据一秒钟都不用，查询速度提高了至少20倍，而且因为是虚拟关联表，可以直接用 temp.roleNames like'%角色1%' 而实现模糊查询。&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 19 Aug 2022 08:33:00 GMT</pubDate>
    </item>
    <item>
      <title>MySQL创建用户与授权</title>
      <link>https://maruifu.cn/article/244</link>
      <content:encoded>&lt;h2&gt;创建用户&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;CREATE USER 'username'@'host' IDENTIFIED BY 'password'; &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;username：你将创建的用户名&lt;/li&gt; &lt;li&gt;host：指定该用户在哪个主机上可以登陆，如果是本地用户可用localhost，如果想让该用户可以&lt;strong&gt;从任意远程主机登陆&lt;/strong&gt;，可以使用通配符&lt;code&gt;%&lt;/code&gt;&lt;/li&gt; &lt;li&gt;password：该用户的登陆密码，密码可以为空，如果为空则该用户可以不需要密码登陆服务器&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h3&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE USER 'dog'@'localhost' IDENTIFIED BY '123456'; CREATE USER 'pig'@'192.168.1.101_' IDENDIFIED BY '123456'; CREATE USER 'pig'@'%' IDENTIFIED BY '123456'; CREATE USER 'pig'@'%' IDENTIFIED BY ''; CREATE USER 'pig'@'%'; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;&lt;strong&gt;授权&lt;/strong&gt;&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;GRANT privileges ON databasename.tablename TO 'username'@'host' &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;privileges：用户的操作权限，如&lt;code&gt;SELECT&lt;/code&gt;，&lt;code&gt;INSERT&lt;/code&gt;，&lt;code&gt;UPDATE&lt;/code&gt;等，如果要授予所的权限则使用&lt;code&gt;ALL&lt;/code&gt;&lt;/li&gt; &lt;li&gt;databasename：数据库名&lt;/li&gt; &lt;li&gt;tablename：表名，如果要授予该用户对所有数据库和表的相应操作权限则可用&lt;code&gt;*&lt;/code&gt;表示，如&lt;code&gt;*.*&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h3&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;GRANT SELECT, INSERT ON test.user TO 'pig'@'%'; GRANT ALL ON *.* TO 'pig'@'%'; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;用以上命令授权的用户不能给其它用户授权，如果想让该用户可以授权，用以下命令:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTION; &lt;/code&gt;&lt;/pre&gt; &lt;/blockquote&gt; &lt;h2&gt;设置与更改用户密码&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果是当前登陆用户用:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;SET PASSWORD = PASSWORD(&amp;quot;newpassword&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;SET PASSWORD FOR 'pig'@'%' = PASSWORD(&amp;quot;123456&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;撤销用户权限&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;REVOKE privilege ON databasename.tablename FROM 'username'@'host'; &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;privilege, databasename, tablename：同授权部分&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;REVOKE SELECT ON *.* FROM 'pig'@'%'; &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;假如你在给用户&lt;code&gt;'pig'@'%'&lt;/code&gt;授权的时候是这样的（或类似的）：&lt;code&gt;GRANT SELECT ON test.user TO 'pig'@'%'&lt;/code&gt;，则在使用&lt;code&gt;REVOKE SELECT ON *.* FROM 'pig'@'%';&lt;/code&gt;命令并不能撤销该用户对test数据库中user表的&lt;code&gt;SELECT&lt;/code&gt; 操作。&lt;/p&gt; &lt;p&gt;相反，如果授权使用的是&lt;code&gt;GRANT SELECT ON *.* TO 'pig'@'%';&lt;/code&gt;则&lt;code&gt;REVOKE SELECT ON test.user FROM 'pig'@'%';&lt;/code&gt;命令也不能撤销该用户对test数据库中user表的&lt;code&gt;Select&lt;/code&gt;权限。&lt;/p&gt; &lt;p&gt;具体信息可以用命令&lt;code&gt;SHOW GRANTS FOR 'pig'@'%';&lt;/code&gt; 查看。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;删除用户&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;DROP USER 'username'@'host'; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 12 Aug 2022 09:02:00 GMT</pubDate>
    </item>
    <item>
      <title>centos7 挂载未分配的硬盘空间</title>
      <link>https://maruifu.cn/article/243</link>
      <content:encoded>&lt;h1&gt;前言&lt;/h1&gt; &lt;p&gt;最近在做提供虚拟机的工作时发现，vcenter的模板虽然可以快速创建出和模板一模一样的虚拟机，但是由于对硬盘的配置每个人的要求不同，vmware只支持扩大硬盘配置。&lt;/p&gt; &lt;p&gt;故在做模板时，硬盘设计的小些，然后根据每个人不同的需求再做磁盘的扩容就好了。&lt;/p&gt; &lt;h1&gt;场景&lt;/h1&gt; &lt;p&gt;虚拟机初始硬盘：16G&lt;/p&gt; &lt;p&gt;虚拟机扩容后硬盘：50G&lt;/p&gt; &lt;p&gt;需求：将扩容的34G空间增加到文件系统/dev/mapper/centos-root中&lt;/p&gt; &lt;hr /&gt; &lt;h1&gt;扩容文件系统&lt;/h1&gt; &lt;h2&gt;确认硬盘空间&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;列出块设备信息 &lt;code&gt;lsblk&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;查看文件系统的硬盘使用&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;df -h &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223454385-1500348434.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h4&gt;查看硬盘数量和分区情况&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;fdisk -l &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223524665-1667458707.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;对未分配的空间进行分区&lt;/h2&gt; &lt;h4&gt;创建新分区&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;fdisk /dev/sda &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;新建分区&lt;/h4&gt; &lt;p&gt;输入“n”，回车；（n：新建分区）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223619826-213656039.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h4&gt;主分区&lt;/h4&gt; &lt;p&gt;不用输入，回车；（p：主分区）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223720803-1372127112.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;设置扇区&lt;/h3&gt; &lt;p&gt;下面几个选项不用输入，回车；&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223743514-1001627085.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;设置分区号&lt;/h3&gt; &lt;p&gt;输入“t”，回车；接着不用输入，回车；（t：设置分区号）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223826630-161864888.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;设置分区格式&lt;/h3&gt; &lt;p&gt;输入“L”，回车；接着输入“8e”，回车；（8e：指定分区格式为Linux LVM）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223858001-1752780240.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;保存修改&lt;/h3&gt; &lt;p&gt;输入“w”，回车；（w：保存修改）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728223927949-480212545.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;重启虚拟机&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;reboot &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看新的分区情况（新的分区/dev/sda3）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;fdisk -l &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224114969-438389940.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;对目标分区扩容&lt;/h2&gt; &lt;h3&gt;创建物理卷&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;pvcreate /dev/sda3 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224227786-434593613.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;添加物理卷&lt;/h3&gt; &lt;p&gt;添加物理卷（/dev/sda3）到卷组（centos）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vgextend centos /dev/sda3 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224308723-525649633.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;卷组属性&lt;/h3&gt; &lt;p&gt;查看centos卷组的属性&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vgdisplay &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224340385-2050358381.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;可以看到有不到34G的空闲空间可以扩展。&lt;/p&gt; &lt;h3&gt;分配空间&lt;/h3&gt; &lt;p&gt;将空闲的空间都分配给root文件系统&lt;/p&gt; &lt;pre&gt;&lt;code&gt;lvextend -l +100%FREE /dev/mapper/centos-root &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224430324-1079333576.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;扩容&lt;/h3&gt; &lt;p&gt;对root文件系统执行扩容&lt;/p&gt; &lt;pre&gt;&lt;code&gt;xfs_growfs /dev/mapper/centos-root &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224503743-1368873189.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;查看扩容结果&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;df -h &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/07/14/901201-20190728224543028-773070208.png" alt="img" title="img" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 14 Jul 2022 03:17:00 GMT</pubDate>
    </item>
    <item>
      <title>IDEA 远程调试</title>
      <link>https://maruifu.cn/article/242</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;IDEA 远程调试，像运行本地代码一样调试远程主机上的程序，以排查远程程序的BUG或代码执行流程。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;概述&lt;/h2&gt; &lt;p&gt;原理：本机和远程主机的两个 VM 之间使用 Debug 协议通过 Socket 通信，传递调试指令和调试信息。 被调试程序的远程虚拟机：作为 Debug 服务端，监听 Debug 调试指令。jdwp是Java Debug Wire Protocol的缩写。 调试程序的本地虚拟机：IDEA 中配置的 Remote Server，指定 Debug 服务器的Host:Port，以供 Debug 客户端程序连接。&lt;/p&gt; &lt;h2&gt;设置&lt;/h2&gt; &lt;h3&gt;打开防火墙&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#查看想开的端口是否已开： firewall-cmd --query-port=20400/tcp #添加指定需要开放的端口： firewall-cmd --add-port=20400/tcp --permanent #重载入添加的端口： firewall-cmd --reload #移除指定端口： firewall-cmd --permanent --remove-port=20400/tcp &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;远程服务中开启 Debug 服务&lt;/h3&gt; &lt;p&gt;对于 SpringBoot命令行添加选项，并重启&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=20400 -jar xxx.jar &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;对于 Tomcat 启动脚本中添加选项，并重启：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;JAVA_OPTS=&amp;quot;$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=20400&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;IDEA 中指定 Debug 服务器&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;点击主窗口菜单 &lt;code&gt;Run / Edit Configurations&lt;/code&gt;，打开“Run/Debug Configurations”窗口；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;点击工具栏上的“+”按钮，下拉菜单中选择“Remote”或者“Remote JVM Debug”；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;设置 Host 为远程服务器的域名或IP，设置端口 Port=20400；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;复制命令行参数，形如 &lt;code&gt;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=20400&lt;/code&gt; 这个参数就是启动服务器项目时加的参数&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/06/09/image-20220609101503190.png" alt="image-20220609101503190" title="image-20220609101503190" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 09 Jun 2022 02:32:00 GMT</pubDate>
    </item>
    <item>
      <title>build-helper-maven-plugin 简单讲解</title>
      <link>https://maruifu.cn/article/241</link>
      <content:encoded>&lt;h2&gt;简介&lt;/h2&gt; &lt;h3&gt;官方文档&lt;/h3&gt; &lt;p&gt;https://www.mojohaus.org/build-helper-&lt;a href="https://so.csdn.net/so/search?q=maven&amp;amp;spm=1001.2101.3001.7020" target="_blank"&gt;maven&lt;/a&gt;-plugin/index.html&lt;/p&gt; &lt;h2&gt;常用的Goals&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;build-helper:add-source&lt;/td&gt;&lt;td&gt;添加一个或者多个目录到POM.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:add-test-source&lt;/td&gt;&lt;td&gt;添加测试目录到 POM.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:add-resource&lt;/td&gt;&lt;td&gt;添加资源目录到POM.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:add-test-resource&lt;/td&gt;&lt;td&gt;添加测试资源目录到POM.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:attach-artifact&lt;/td&gt;&lt;td&gt;附加要安装和部署的其他部件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:maven-version&lt;/td&gt;&lt;td&gt;设置一个包含当前版本的 maven 的属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:regex-property&lt;/td&gt;&lt;td&gt;通过将正则表达式替换规则应用于提供的值来设置属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:regex-properties&lt;/td&gt;&lt;td&gt;通过将正则表达式替换规则应用于提供的值来设置属性.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:released-version&lt;/td&gt;&lt;td&gt;解决本项目最新发布的版本.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:parse-version&lt;/td&gt;&lt;td&gt;将版本解析为不同的属性.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:remove-project-artifact&lt;/td&gt;&lt;td&gt;从本地存储库中删除项目的工件.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:reserve-network-port&lt;/td&gt;&lt;td&gt;保留随机和未使用的网络端口列表.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:local-ip&lt;/td&gt;&lt;td&gt;检索当前主机 IP 地址.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:hostname&lt;/td&gt;&lt;td&gt;检索当前主机名.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:cpu-count&lt;/td&gt;&lt;td&gt;检索可用 CPU 的数量.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:timestamp-property&lt;/td&gt;&lt;td&gt;根据当前日期和时间设置属性.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:uptodate-property&lt;/td&gt;&lt;td&gt;根据文件集的输出相对于其输入是否是最新的来设置属性.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:uptodate-properties&lt;/td&gt;&lt;td&gt;根据多个文件集的输出相对于它们的输入是否是最新的来设置多个属性.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build-helper:rootlocation&lt;/td&gt;&lt;td&gt;设置定义多模块构建的根文件夹的属性.&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;简单用法&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;project&amp;gt;     &amp;lt;build&amp;gt;     &amp;lt;plugins&amp;gt;   &amp;lt;!--独立打包--&amp;gt;              &amp;lt;plugin&amp;gt;                 &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;2.1.1.RELEASE&amp;lt;/version&amp;gt;                 &amp;lt;configuration&amp;gt;                     &amp;lt;fork&amp;gt;true&amp;lt;/fork&amp;gt; &amp;lt;!-- 如果没有该配置，devtools不会生效 --&amp;gt;                 &amp;lt;/configuration&amp;gt;                 &amp;lt;executions&amp;gt;                     &amp;lt;execution&amp;gt;                         &amp;lt;goals&amp;gt;                             &amp;lt;goal&amp;gt;repackage&amp;lt;/goal&amp;gt;                         &amp;lt;/goals&amp;gt;                     &amp;lt;/execution&amp;gt;                 &amp;lt;/executions&amp;gt;             &amp;lt;/plugin&amp;gt;  &amp;lt;!-- domain 打包进去--&amp;gt;       &amp;lt;plugin&amp;gt;         &amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;build-helper-maven-plugin&amp;lt;/artifactId&amp;gt;         &amp;lt;version&amp;gt;3.3.0&amp;lt;/version&amp;gt;         &amp;lt;executions&amp;gt;           &amp;lt;execution&amp;gt;             &amp;lt;!--id是必须的，常常和goals是一样的--&amp;gt;             &amp;lt;id&amp;gt;add-source&amp;lt;/id&amp;gt;             &amp;lt;phase&amp;gt;generate-sources&amp;lt;/phase&amp;gt;             &amp;lt;goals&amp;gt;               &amp;lt;goal&amp;gt;add-source&amp;lt;/goal&amp;gt;             &amp;lt;/goals&amp;gt;             &amp;lt;!--在configuration中设置goals的具体属性--&amp;gt;             &amp;lt;configuration&amp;gt;               &amp;lt;!--这些熟悉可以通过文档获得--&amp;gt;               &amp;lt;sources&amp;gt;                 &amp;lt;source&amp;gt;${basedir}/src/main/java&amp;lt;/source&amp;gt;                 &amp;lt;source&amp;gt;${basedir}/src/main/domain&amp;lt;/source&amp;gt;                 ...               &amp;lt;/sources&amp;gt;             &amp;lt;/configuration&amp;gt;           &amp;lt;/execution&amp;gt;           &amp;lt;!--在比如下面的，可以获得当前时间--&amp;gt;           &amp;lt;execution&amp;gt;             &amp;lt;id&amp;gt;timestamp-property&amp;lt;/id&amp;gt;             &amp;lt;goals&amp;gt;               &amp;lt;goal&amp;gt;timestamp-property&amp;lt;/goal&amp;gt;             &amp;lt;/goals&amp;gt;               &amp;lt;configuration&amp;gt;                   &amp;lt;name&amp;gt;current.time&amp;lt;/name&amp;gt;                   &amp;lt;pattern&amp;gt;yyyyMMddHHmmss&amp;lt;/pattern&amp;gt;                   &amp;lt;timeZone&amp;gt;GMT+8&amp;lt;/timeZone&amp;gt;               &amp;lt;/configuration&amp;gt;           &amp;lt;/execution&amp;gt;         &amp;lt;/executions&amp;gt;       &amp;lt;/plugin&amp;gt;     &amp;lt;/plugins&amp;gt;   &amp;lt;/build&amp;gt; &amp;lt;/project&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;获得当前时间的那个，可以通过configuration的name在pom的其他地方通过${current.time}来引用当前时间&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Wed, 08 Jun 2022 06:20:00 GMT</pubDate>
    </item>
    <item>
      <title>ThinkPad系列 win10系统没有声音问题完美解决</title>
      <link>https://maruifu.cn/article/240</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;首先说明一下自己电脑的情况，，在thinkpad官网下载安装好声卡驱动后还是没有声音 ，检测的时候也会根据声音大小跳动，我测试蓝牙耳机连接后声音没问题， 一度让我怀疑我的笔记本扬声器坏了 ，&lt;/p&gt; &lt;p&gt;最近疫情严重了大家都居家隔离都在家办公，经常有开会分享屏幕，手机接听就太小屏幕看不见，电脑接听没有声音，让我企业微信分享屏幕，个人微信 开启语音聊天。。。。。。,后来发现笔记本电脑上f1键亮着 上面有个小喇叭，想关也关不掉 了不废话了看怎么解决的。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;下载驱动&lt;/h2&gt; &lt;p&gt;去联想官网下载对应电脑型号的声卡驱动，官网地址：&lt;a href="https://think.lenovo.com.cn/support/driver/mainpage.aspx#ThinkPad" target="_blank"&gt;驱动下载_ThinkPad服务网站-联想服务&lt;/a&gt;，进入联想thinkpad官网后找到服务里面的驱动下载选项，然后点击进去，如下图:、&lt;/p&gt; &lt;p&gt;然后会进入到下个界面，如下图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/25/_1653443950557.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;在这个界面，要下搜索框里输入自己的电脑主机编号，电脑主机编号一般都在电脑的背面有显示，或者在页面下方选择自己的电脑型号进去相应的驱动页面进行下载，要不然下载个联想驱动管理也可以查看自己的电脑型号，然后才能进入到自己电脑型号所对应的所有驱动的下载的地方，一般来说都是最新版的驱动，如下图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/25/image-20220525100051437.png" alt="image-20220525100051437" title="image-20220525100051437" /&gt;&lt;/p&gt; &lt;p&gt;在这个界面就可以下载需要的驱动了，就是这个版本的声卡驱动，下载下来，直接运行安装就OK了，安装完驱动后，重启电脑才有效果。后来经过我的实践证明，只安装这个版本的声卡驱动是没有效果的，还是没有声音，必须要安装上图里的热键驱动才可以。&lt;/p&gt; &lt;p&gt;就这样，去联想官网下载对应版本的声卡驱动，还有热键驱动，安装后重启电脑，就有声音了。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;其他问题可以参照 &lt;a href="https://support.microsoft.com/zh-cn/windows/%E4%BF%AE%E5%A4%8D-windows-10-%E4%B8%AD%E7%9A%84%E5%A3%B0%E9%9F%B3%E9%97%AE%E9%A2%98-73025246-b61c-40fb-671a-2535c7cd56c8" target="_blank"&gt;修复 Windows 10 中的声音问题&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Wed, 25 May 2022 02:05:00 GMT</pubDate>
    </item>
    <item>
      <title>logback 配置文件</title>
      <link>https://maruifu.cn/article/239</link>
      <content:encoded>&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;!-- 日志级别从低到高分为TRACE &amp;lt; DEBUG &amp;lt; INFO &amp;lt; WARN &amp;lt; ERROR &amp;lt; FATAL，如果设置为WARN，则低于WARN的信息都不会输出 --&amp;gt; &amp;lt;!-- scan:当此属性设置为true时，配置文档如果发生改变，将会被重新加载，默认值为true --&amp;gt; &amp;lt;!-- scanPeriod:设置监测配置文档是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。 当scan为true时，此属性生效。默认的时间间隔为1分钟。 --&amp;gt; &amp;lt;!-- debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。 --&amp;gt; &amp;lt;configuration scan=&amp;quot;true&amp;quot; scanPeriod=&amp;quot;60 seconds&amp;quot;  debug=&amp;quot;false&amp;quot;&amp;gt;  &amp;lt;!-- contextName:用来设置上下文名称，每个logger都关联到logger上下文，默认上下文名称为default。但可以使用&amp;lt;contextName&amp;gt;设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。--&amp;gt;   &amp;lt;contextName&amp;gt;myApp&amp;lt;/contextName&amp;gt;   &amp;lt;!-- &amp;lt;springProperty scope=&amp;quot;context&amp;quot; name=&amp;quot;logPath&amp;quot; source=&amp;quot;logging.path&amp;quot;    defaultValue=&amp;quot;logs&amp;quot;/&amp;gt; --&amp;gt;  &amp;lt;!-- name的值是变量的名称，value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后，可以使“${}”来使用变量。 --&amp;gt;  &amp;lt;property name=&amp;quot;log.path&amp;quot; value=&amp;quot;logs/&amp;quot; /&amp;gt;   &amp;lt;!--0. 日志格式和颜色渲染 --&amp;gt;  &amp;lt;!-- 彩色日志依赖的渲染类 --&amp;gt;  &amp;lt;conversionRule conversionWord=&amp;quot;clr&amp;quot;   converterClass=&amp;quot;org.springframework.boot.logging.logback.ColorConverter&amp;quot; /&amp;gt;  &amp;lt;conversionRule conversionWord=&amp;quot;wex&amp;quot;   converterClass=&amp;quot;org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter&amp;quot; /&amp;gt;  &amp;lt;conversionRule conversionWord=&amp;quot;wEx&amp;quot;   converterClass=&amp;quot;org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter&amp;quot; /&amp;gt;  &amp;lt;!-- 彩色日志格式 --&amp;gt;  &amp;lt;property name=&amp;quot;CONSOLE_LOG_PATTERN&amp;quot;   value=&amp;quot;${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH🇲🇲ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}&amp;quot; /&amp;gt;   &amp;lt;!--1. 输出到控制台 --&amp;gt;  &amp;lt;appender name=&amp;quot;CONSOLE&amp;quot;   class=&amp;quot;ch.qos.logback.core.ConsoleAppender&amp;quot;&amp;gt;   &amp;lt;!-- 此日志appender是为开发使用，只配置最底级别，控制台输出的日志级别是大于或等于此级别的日志信息 --&amp;gt;   &amp;lt;filter class=&amp;quot;ch.qos.logback.classic.filter.ThresholdFilter&amp;quot;&amp;gt;    &amp;lt;level&amp;gt;debug&amp;lt;/level&amp;gt;   &amp;lt;/filter&amp;gt;   &amp;lt;encoder&amp;gt;    &amp;lt;Pattern&amp;gt;${CONSOLE_LOG_PATTERN}&amp;lt;/Pattern&amp;gt;    &amp;lt;!-- 设置字符集 --&amp;gt;    &amp;lt;charset&amp;gt;UTF-8&amp;lt;/charset&amp;gt;   &amp;lt;/encoder&amp;gt;  &amp;lt;/appender&amp;gt;     &amp;lt;!--2. 输出到文档 --&amp;gt;  &amp;lt;!-- 2.1 level为 DEBUG 日志，时间滚动输出 --&amp;gt;  &amp;lt;appender name=&amp;quot;DEBUG_FILE&amp;quot;   class=&amp;quot;ch.qos.logback.core.rolling.RollingFileAppender&amp;quot;&amp;gt;   &amp;lt;!-- 正在记录的日志文档的路径及文档名 --&amp;gt;   &amp;lt;file&amp;gt;${log.path}/web_debug.log&amp;lt;/file&amp;gt;   &amp;lt;!--日志文档输出格式 --&amp;gt;   &amp;lt;encoder&amp;gt;    &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH🇲🇲ss.SSS} [%thread] %-5level %logger{50} -     %msg%n&amp;lt;/pattern&amp;gt;    &amp;lt;charset&amp;gt;UTF-8&amp;lt;/charset&amp;gt; &amp;lt;!-- 设置字符集 --&amp;gt;   &amp;lt;/encoder&amp;gt;   &amp;lt;!-- 日志记录器的滚动策略，按日期，按大小记录 --&amp;gt;   &amp;lt;rollingPolicy    class=&amp;quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&amp;quot;&amp;gt;    &amp;lt;!-- 日志归档 --&amp;gt;    &amp;lt;fileNamePattern&amp;gt;${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log    &amp;lt;/fileNamePattern&amp;gt;    &amp;lt;timeBasedFileNamingAndTriggeringPolicy     class=&amp;quot;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&amp;quot;&amp;gt;     &amp;lt;maxFileSize&amp;gt;100MB&amp;lt;/maxFileSize&amp;gt;    &amp;lt;/timeBasedFileNamingAndTriggeringPolicy&amp;gt;    &amp;lt;!--日志文档保留天数 --&amp;gt;    &amp;lt;maxHistory&amp;gt;15&amp;lt;/maxHistory&amp;gt;   &amp;lt;/rollingPolicy&amp;gt;   &amp;lt;!-- 此日志文档只记录debug级别的 --&amp;gt;   &amp;lt;filter class=&amp;quot;ch.qos.logback.classic.filter.LevelFilter&amp;quot;&amp;gt;    &amp;lt;level&amp;gt;debug&amp;lt;/level&amp;gt;    &amp;lt;onMatch&amp;gt;ACCEPT&amp;lt;/onMatch&amp;gt;    &amp;lt;onMismatch&amp;gt;DENY&amp;lt;/onMismatch&amp;gt;   &amp;lt;/filter&amp;gt;  &amp;lt;/appender&amp;gt;   &amp;lt;!-- 2.2 level为 INFO 日志，时间滚动输出 --&amp;gt;  &amp;lt;appender name=&amp;quot;INFO_FILE&amp;quot;   class=&amp;quot;ch.qos.logback.core.rolling.RollingFileAppender&amp;quot;&amp;gt;   &amp;lt;!-- 正在记录的日志文档的路径及文档名 --&amp;gt;   &amp;lt;file&amp;gt;${log.path}/web_info.log&amp;lt;/file&amp;gt;   &amp;lt;!--日志文档输出格式 --&amp;gt;   &amp;lt;encoder&amp;gt;    &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH🇲🇲ss.SSS} [%thread] %-5level %logger{50} -     %msg%n&amp;lt;/pattern&amp;gt;    &amp;lt;charset&amp;gt;UTF-8&amp;lt;/charset&amp;gt;   &amp;lt;/encoder&amp;gt;   &amp;lt;!-- 日志记录器的滚动策略，按日期，按大小记录 --&amp;gt;   &amp;lt;rollingPolicy    class=&amp;quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&amp;quot;&amp;gt;    &amp;lt;!-- 每天日志归档路径以及格式 --&amp;gt;    &amp;lt;fileNamePattern&amp;gt;${log.path}/web-info-%d{yyyy-MM-dd}.%i.log    &amp;lt;/fileNamePattern&amp;gt;    &amp;lt;timeBasedFileNamingAndTriggeringPolicy     class=&amp;quot;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&amp;quot;&amp;gt;     &amp;lt;maxFileSize&amp;gt;100MB&amp;lt;/maxFileSize&amp;gt;    &amp;lt;/timeBasedFileNamingAndTriggeringPolicy&amp;gt;    &amp;lt;!--日志文档保留天数 --&amp;gt;    &amp;lt;maxHistory&amp;gt;15&amp;lt;/maxHistory&amp;gt;   &amp;lt;/rollingPolicy&amp;gt;   &amp;lt;!-- 此日志文档只记录info级别的 --&amp;gt;   &amp;lt;filter class=&amp;quot;ch.qos.logback.classic.filter.LevelFilter&amp;quot;&amp;gt;    &amp;lt;level&amp;gt;info&amp;lt;/level&amp;gt;    &amp;lt;onMatch&amp;gt;ACCEPT&amp;lt;/onMatch&amp;gt;    &amp;lt;onMismatch&amp;gt;DENY&amp;lt;/onMismatch&amp;gt;   &amp;lt;/filter&amp;gt;  &amp;lt;/appender&amp;gt;   &amp;lt;!-- 2.3 level为 WARN 日志，时间滚动输出 --&amp;gt;  &amp;lt;appender name=&amp;quot;WARN_FILE&amp;quot;   class=&amp;quot;ch.qos.logback.core.rolling.RollingFileAppender&amp;quot;&amp;gt;   &amp;lt;!-- 正在记录的日志文档的路径及文档名 --&amp;gt;   &amp;lt;file&amp;gt;${log.path}/web_warn.log&amp;lt;/file&amp;gt;   &amp;lt;!--日志文档输出格式 --&amp;gt;   &amp;lt;encoder&amp;gt;    &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH🇲🇲ss.SSS} [%thread] %-5level %logger{50} -     %msg%n&amp;lt;/pattern&amp;gt;    &amp;lt;charset&amp;gt;UTF-8&amp;lt;/charset&amp;gt; &amp;lt;!-- 此处设置字符集 --&amp;gt;   &amp;lt;/encoder&amp;gt;   &amp;lt;!-- 日志记录器的滚动策略，按日期，按大小记录 --&amp;gt;   &amp;lt;rollingPolicy    class=&amp;quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&amp;quot;&amp;gt;    &amp;lt;fileNamePattern&amp;gt;${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log    &amp;lt;/fileNamePattern&amp;gt;    &amp;lt;timeBasedFileNamingAndTriggeringPolicy     class=&amp;quot;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&amp;quot;&amp;gt;     &amp;lt;maxFileSize&amp;gt;100MB&amp;lt;/maxFileSize&amp;gt;    &amp;lt;/timeBasedFileNamingAndTriggeringPolicy&amp;gt;    &amp;lt;!--日志文档保留天数 --&amp;gt;    &amp;lt;maxHistory&amp;gt;15&amp;lt;/maxHistory&amp;gt;   &amp;lt;/rollingPolicy&amp;gt;   &amp;lt;!-- 此日志文档只记录warn级别的 --&amp;gt;   &amp;lt;filter class=&amp;quot;ch.qos.logback.classic.filter.LevelFilter&amp;quot;&amp;gt;    &amp;lt;level&amp;gt;warn&amp;lt;/level&amp;gt;    &amp;lt;onMatch&amp;gt;ACCEPT&amp;lt;/onMatch&amp;gt;    &amp;lt;onMismatch&amp;gt;DENY&amp;lt;/onMismatch&amp;gt;   &amp;lt;/filter&amp;gt;  &amp;lt;/appender&amp;gt;   &amp;lt;!-- 2.4 level为 ERROR 日志，时间滚动输出 --&amp;gt;  &amp;lt;appender name=&amp;quot;ERROR_FILE&amp;quot;   class=&amp;quot;ch.qos.logback.core.rolling.RollingFileAppender&amp;quot;&amp;gt;   &amp;lt;!-- 正在记录的日志文档的路径及文档名 --&amp;gt;   &amp;lt;file&amp;gt;${log.path}/web_error.log&amp;lt;/file&amp;gt;   &amp;lt;!--日志文档输出格式 --&amp;gt;   &amp;lt;encoder&amp;gt;    &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH🇲🇲ss.SSS} [%thread] %-5level %logger{50} -     %msg%n&amp;lt;/pattern&amp;gt;    &amp;lt;charset&amp;gt;UTF-8&amp;lt;/charset&amp;gt; &amp;lt;!-- 此处设置字符集 --&amp;gt;   &amp;lt;/encoder&amp;gt;   &amp;lt;!-- 日志记录器的滚动策略，按日期，按大小记录 --&amp;gt;   &amp;lt;rollingPolicy    class=&amp;quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&amp;quot;&amp;gt;    &amp;lt;fileNamePattern&amp;gt;${log.path}/web-error-%d{yyyy-MM-dd}.%i.log    &amp;lt;/fileNamePattern&amp;gt;    &amp;lt;timeBasedFileNamingAndTriggeringPolicy     class=&amp;quot;ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP&amp;quot;&amp;gt;     &amp;lt;maxFileSize&amp;gt;100MB&amp;lt;/maxFileSize&amp;gt;    &amp;lt;/timeBasedFileNamingAndTriggeringPolicy&amp;gt;    &amp;lt;!--日志文档保留天数 --&amp;gt;    &amp;lt;maxHistory&amp;gt;15&amp;lt;/maxHistory&amp;gt;   &amp;lt;/rollingPolicy&amp;gt;   &amp;lt;!-- 此日志文档只记录ERROR级别的 --&amp;gt;   &amp;lt;filter class=&amp;quot;ch.qos.logback.classic.filter.LevelFilter&amp;quot;&amp;gt;    &amp;lt;level&amp;gt;ERROR&amp;lt;/level&amp;gt;    &amp;lt;onMatch&amp;gt;ACCEPT&amp;lt;/onMatch&amp;gt;    &amp;lt;onMismatch&amp;gt;DENY&amp;lt;/onMismatch&amp;gt;   &amp;lt;/filter&amp;gt;  &amp;lt;/appender&amp;gt;    &amp;lt;!-- &amp;lt;logger&amp;gt;用来设置某一个包或者具体的某一个类的日志打印级别、 以及指定&amp;lt;appender&amp;gt;。&amp;lt;logger&amp;gt;仅有一个name属性，    一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别，大小写无关：TRACE,    DEBUG, INFO, WARN, ERROR, ALL 和 OFF， 还有一个特俗值INHERITED或者同义词NULL，代表强制执行上级的级别。    如果未设置此属性，那么当前logger将会继承上级的级别。 addtivity:是否向上级logger传递打印信息。默认是true。 &amp;lt;logger    name=&amp;quot;org.springframework.web&amp;quot; level=&amp;quot;info&amp;quot;/&amp;gt; &amp;lt;logger name=&amp;quot;org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor&amp;quot;    level=&amp;quot;INFO&amp;quot;/&amp;gt; --&amp;gt;   &amp;lt;!-- 使用mybatis的时候，sql语句是debug下才会打印，而这里我们只配置了info，所以想要查看sql语句的话，有以下两种操作：    第一种把&amp;lt;root level=&amp;quot;info&amp;quot;&amp;gt;改成&amp;lt;root level=&amp;quot;DEBUG&amp;quot;&amp;gt;这样就会打印sql，不过这样日志那边会出现很多其他消息    第二种就是单独给dao下目录配置debug模式，代码如下，这样配置sql语句会打印，其他还是正常info级别： 【logging.level.org.mybatis=debug    logging.level.dao=debug】 --&amp;gt;   &amp;lt;!-- root节点是必选节点，用来指定最基础的日志输出级别，只有一个level属性 level:用来设置打印级别，大小写无关：TRACE,    DEBUG, INFO, WARN, ERROR, ALL 和 OFF， 不能设置为INHERITED或者同义词NULL。默认是DEBUG 可以包含零个或多个元素，标识这个appender将会添加到这个logger。 --&amp;gt;       &amp;lt;!-- 常用logger配置 --&amp;gt; &amp;lt;!-- show parameters for hibernate sql 专为 Hibernate 定制 --&amp;gt; &amp;lt;logger name=&amp;quot;org.hibernate.type.descriptor.sql.BasicBinder&amp;quot; level=&amp;quot;TRACE&amp;quot; /&amp;gt; &amp;lt;logger name=&amp;quot;org.hibernate.type.descriptor.sql.BasicExtractor&amp;quot; level=&amp;quot;DEBUG&amp;quot; /&amp;gt; &amp;lt;logger name=&amp;quot;org.hibernate.SQL&amp;quot; level=&amp;quot;DEBUG&amp;quot; /&amp;gt; &amp;lt;logger name=&amp;quot;org.hibernate.engine.QueryParameters&amp;quot; level=&amp;quot;DEBUG&amp;quot; /&amp;gt; &amp;lt;logger name=&amp;quot;org.hibernate.engine.query.HQLQueryPlan&amp;quot; level=&amp;quot;DEBUG&amp;quot; /&amp;gt;  &amp;lt;!--myibatis log configure--&amp;gt; &amp;lt;logger name=&amp;quot;com.apache.ibatis&amp;quot; level=&amp;quot;TRACE&amp;quot;/&amp;gt; &amp;lt;logger name=&amp;quot;java.sql.Connection&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;gt; &amp;lt;logger name=&amp;quot;java.sql.Statement&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;gt; &amp;lt;logger name=&amp;quot;java.sql.PreparedStatement&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;gt;   &amp;lt;!--打印sql配置--&amp;gt; &amp;lt;logger name=&amp;quot;cn.maruifu.myAppName.module.dao&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;gt;     &amp;lt;!-- 4. 最终的策略 --&amp;gt;  &amp;lt;!-- 4.1 开发环境:打印控制台 --&amp;gt;  &amp;lt;springProfile name=&amp;quot;dev&amp;quot;&amp;gt;   &amp;lt;root level=&amp;quot;info&amp;quot;&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;CONSOLE&amp;quot; /&amp;gt;   &amp;lt;/root&amp;gt;  &amp;lt;/springProfile&amp;gt;    &amp;lt;!-- 4.2 生产环境:输出到文档 --&amp;gt;  &amp;lt;springProfile name=&amp;quot;prod&amp;quot;&amp;gt;   &amp;lt;root level=&amp;quot;info&amp;quot;&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;CONSOLE&amp;quot; /&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;DEBUG_FILE&amp;quot; /&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;INFO_FILE&amp;quot; /&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;WARN_FILE&amp;quot; /&amp;gt;    &amp;lt;appender-ref ref=&amp;quot;ERROR_FILE&amp;quot; /&amp;gt;   &amp;lt;/root&amp;gt;  &amp;lt;/springProfile&amp;gt;  &amp;lt;/configuration&amp;gt; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 04 May 2022 11:38:06 GMT</pubDate>
    </item>
    <item>
      <title>十年老架构师总结：性能优化其实不难，记住这十条策略就够了</title>
      <link>https://maruifu.cn/article/238</link>
      <content:encoded>&lt;p&gt;我们聊了性能优化的六大原则。原则有了，但是在针对实际的性能问题的时候，用什么样的解决方案才可以提升性能呢？这就需要你了解&lt;strong&gt;具体的优化策略&lt;/strong&gt;了。&lt;/p&gt; &lt;p&gt;现实中的性能问题和具体领域千差万别，我也不可能面面俱到。但是为了帮助你理解，我总结了十大常用的优化策略。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/162071412d1a10d2f42d.jpg" alt="1620" title="1620" /&gt;&lt;/p&gt; &lt;p&gt;我将这十大策略分成五个类别，每个类别对应两个相关策略，帮助你掌握。这五个类别是：时空相互转换、并行 / 异步操作、预先 / 延后处理、缓存 / 批量合并、算法设计和数据结构。我们现在一个个来讲。&lt;/p&gt; &lt;h1&gt;一、时空转换&lt;/h1&gt; &lt;p&gt;第一个策略类别是“时空转换”。我们看科幻电影和小说的时候，经常会看到时空转换这个题材。性能优化里面有两个策略恰好组成了这个类别，包括“用时间换空间”和“用空间换 时间”这两个看似互相对立的策略。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1. 用时间换空间&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;用时间换空间的策略，出发点是内存和存储这样的“空间”资源，有时会成为最稀缺的资源，所以需要尽量减少占用的空间。比如，一个系统的最大性能瓶颈如果是内存使用量，那么减少内存的使用就是最重要的性能优化。&lt;/p&gt; &lt;p&gt;这个策略具体的操作方法有几种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;改变应用程序本身的数据结构或者数据格式，减少需要存储的数据的大小；&lt;/li&gt; &lt;li&gt;想方设法压缩存在内存中的数据，比如采用某种压缩算法，真正使用时再解压缩；&lt;/li&gt; &lt;li&gt;把一些内存数据，存放到外部的、更加便宜的存储系统里面，到需要时再取回来。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这些节省内存空间的方法，一般都需要付出时间的代价。&lt;/p&gt; &lt;p&gt;除了内存，还有一种常见的场景是，降低数据的大小来方便网络传输和外部存储。压缩的方法和算法有很多种， 比如现在比较流行的 ZStandard（ZSTD）和 LZ4。这些算法之间有空间和时间的取舍。&lt;/p&gt; &lt;p&gt;衡量任何压缩算法，基本上看三个指标：&lt;strong&gt;压缩比例&lt;/strong&gt;、&lt;strong&gt;压缩速度&lt;/strong&gt;以及&lt;strong&gt;使用内存&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;如果系统的瓶颈在网络传输速度或者存储空间大小上，那就尽量采取高压缩比的算法，这样用时间来换空间，就能够节省时间或者其他方面的成本。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2. 用空间换时间&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;“用空间换时间”就是对“用时间换空间”策略反其道而行之。有些场景下，时间和速度更加重要，但是空间尚有富余，这时我们就可以考虑用空间来换时间。&lt;/p&gt; &lt;p&gt;这里要注意的一点是，我们后面还会讲一条关于使用缓存的策略。虽然缓存的策略理论上也是一种“空间换时间”的方式，但我们在这里把它分开来讲，这是因为缓存策略的“空间”定义与一般的“空间换时间”不同。一般来讲，“缓存”使用的空间，和原来的空间不在同一个层次上，添加的缓存往往比原来的空间高出一个档次。而我们这里“空间换时间”的策略，里面的“空间”是和原来的空间相似的空间。&lt;/p&gt; &lt;p&gt;互联网的服务往往规模很大，比如全国的服务甚至是全球的服务。用户分布在各地，它们对访问时间的要求很高，这就要求被访问的数据和服务，要尽量放在离他们很近的地方。“空间换时间”就是对数据和服务进行多份拷贝，尽可能地完美覆盖大多数的用户。我们前面讲过的 CDN 内容分发网络技术就可以归类于此。&lt;/p&gt; &lt;p&gt;其实我们部署的任何大规模系统，都或多或少地采用了用空间换时间的策略，比如在集群和服务器间进行负载均衡，就是同时用很多个服务器（空间）来换取延迟的减少（时间）。&lt;/p&gt; &lt;h1&gt;二、预先和延后处理&lt;/h1&gt; &lt;p&gt;优化策略的第二大类是“预先和延后处理”，这一类别也有两个互相对立的策略。一个是预先或者提前处理，另外一个是延后或者惰性处理。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3. 预先 / 提前处理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;预先 / 提前处理策略同样也表现在很多领域，比如网站页面资源的提前加载。Web 标准规定了至少两种提前加载的方式：preload 和 prefetch，分别用不同的优先级来加载资源，可以显著地提升页面下载性能。&lt;/p&gt; &lt;p&gt;很多文件系统有预读的功能，就是提前从磁盘读取额外的数据，为下次上层应用程序读数据做准备。这个功能对顺序读取非常有效，可以明显地减少磁盘请求的数量，从而提升读数据的性能。&lt;/p&gt; &lt;p&gt;CPU 和内存也有相应的预取操作，就是将内存中的指令和数据，提前存放到缓存中，从而加快处理器执行速度。缓存预取可以通过硬件或者软件实现，也就是分为&lt;strong&gt;硬件预取&lt;/strong&gt;和&lt;strong&gt;软件预取&lt;/strong&gt;两类。&lt;/p&gt; &lt;p&gt;硬件预取是通过处理器中的硬件来实现的。该硬件会一直监控正在执行程序中请求的指令或数据，并且根据既定规则，识别下一个程序需要的数据或指令并预取。&lt;/p&gt; &lt;p&gt;软件预取是在程序编译的过程中，主动插入预取指令（prefetech），这个预取指令可以是编译器自己加的，也可以是我们加的代码。这样在执行过程中，在指定位置就会进行预取的操作。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4. 延后 / 惰性处理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;延后 / 惰性处理策略和前面说的预先 / 提前处理正好相反。就是尽量将操作（比如计算），推迟到必需执行的时刻，这样很可能避免多余的操作，甚至根本不用操作。&lt;/p&gt; &lt;p&gt;运用这一策略最有名的例子，就是 COW（Copy On Write，写时复制）。假设多个线程都想操作一份数据，一般情况下，每个线程可以自己拷贝一份，放到自己的空间里面。但是拷贝的操作很费时间。系统如果采用惰性处理，就会将拷贝的操作推迟。如果多个线程对这份数据只有读的请求，那么同一个数据资源是可以共享的，因为“读”的操作不会改变这份数据。当某个线程需要修改这一数据时（写操作），系统就将资源拷贝一份给该线程使用，允许改写，这样就不会影响别的线程。&lt;/p&gt; &lt;p&gt;COW 最广为人知的应用场景有两个。一个是 Unix 系统 fork 调用产生的子进程共享父进程的地址空间，只有到某个子进程需要进行写操作才会拷贝一份。另一个是高级语言的类和&lt;/p&gt; &lt;p&gt;容器，比如 Java 中的 CopyOnWrite 容器，用于多线程并发情况下的高效访问。C++ 里面经常使用的 STL 标准模板库中的很多类，比如 string 类，也是具有&lt;strong&gt;写时才拷贝&lt;/strong&gt;技术的类。&lt;/p&gt; &lt;h1&gt;三、并行 / 异步操作&lt;/h1&gt; &lt;p&gt;优化策略的第三大类是“并行 / 异步操作”。并行和异步两种操作虽然看起来很不一样，其实有异曲同工之妙，就是都把一条流水线和处理过程分成了几条，不管是物理上分还是逻辑上分。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5. 并行操作&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;并行操作是一种&lt;strong&gt;物理上&lt;/strong&gt;把一条流水线分成好几条的策略。直观上说，一个人干不完的活，那就多找几个人来干。并行操作既增加了系统的吞吐量，又减少了用户的平均等待时间。比如现代的 CPU 都有很多核，每个核上都可以独立地运行线程，这就是并行操作。&lt;/p&gt; &lt;p&gt;并行操作需要我们的程序有扩展性，不能扩展的程序，就无法进行并行处理。这里的并行概念有不同的粒度，比如是在服务器的粒度（所谓的横向扩展），还是在多线程的粒度，甚至是在指令级别的粒度。&lt;/p&gt; &lt;p&gt;绝大多数互联网服务器，要么使用多进程，要么使用多线程来处理用户的请求，以充分利用多核 CPU。另外一种情况就是在有 IO 阻塞的地方，也是非常适合使用多线程并行操作的，因为这种情况 CPU 基本上是空闲状态，多线程可以让 CPU 多干点活。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6. 异步操作&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;异步操作这一策略和并行操作不同，这是一种&lt;strong&gt;逻辑上&lt;/strong&gt;把一条流水线分成几条的策略。&lt;/p&gt; &lt;p&gt;我们首先在编程的领域澄清一下概念：同步和异步。同步和异步的区别在于一个函数调用之后，是否直接返回结果。如果函数挂起，直到获得结果才返回，这是同步；如果函数马上返回，等数据到达再通知函数，那么这就是异步。&lt;/p&gt; &lt;p&gt;我们知道 Unix 下的文件操作，是有 block 和 non-block 的方式的，有些系统调用也是block 式的，如：Socket 下的 select 等。如果我们的程序一直是同步操作，那么就会非常影响性能。采用异步操作的话，虽然稍微增加一点程序的复杂度，但会让性能的吞吐率有很大提升。&lt;/p&gt; &lt;p&gt;现代的语言往往对异步操作有比较好的支持，使得异步编程变得更加简单，可读性也更好。&lt;/p&gt; &lt;h1&gt;四、缓存 / 批量合并&lt;/h1&gt; &lt;p&gt;“缓存 / 批量合并”是优化策略中的第四大类。缓存和批量合并这两个策略，有些场景下会同时起作用，所以我把它们放在一起。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;7. 缓存数据&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;缓存的本质是加速访问。这是一个用得非常普遍的策略，几乎体现在计算机系统里面每一个模块和领域，CPU、内存、文件系统、存储系统、内容分布、数据库等等，都会遵循这样的策略。&lt;/p&gt; &lt;p&gt;我们最熟悉的应该就是 CPU 的各级缓存了。在文件系统、存储系统和数据库系统里面，也有快速缓存来存储经常访问的数据，目的是尽量提高缓存命中率，从而避免访问比较慢的存储介质。&lt;/p&gt; &lt;p&gt;对于一个基于 Web 的应用服务，前端会有浏览器缓存，有 CDN 存放在边缘服务器上，有反向代理提供的静态内容缓存；后端则还会有服务器本地缓存。&lt;/p&gt; &lt;p&gt;程序设计中，对于可能重复创建和销毁，且创建销毁代价很大的对象（比如套接字和线程），也可以缓存，对应的缓存形式，就是连接池和线程池等。&lt;/p&gt; &lt;p&gt;对于消耗较大的计算，也可以将计算结果缓存起来，下次可以直接读取结果。比如对递归代码的一个有效优化手段，就是缓存中间结果。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8. 批量合并处理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在有 IO（比如网络 IO 和磁盘 IO）的时候，合并操作和批量操作往往能提升吞吐量，提高性能。&lt;/p&gt; &lt;p&gt;我们最常见的是批量 IO 读写。就是在有多次 IO 的时候，可以把它们合并成一次读写数据。这样可以减少读写时间和协议负担。比如，GFS 写文件的时候，尽量批量写，以减少IO 开销。&lt;/p&gt; &lt;p&gt;对数据库的读写操作，也可以尽量合并。比如，对键值数据库的查询，最好一次查询多个键，而不要分成多次。&lt;/p&gt; &lt;p&gt;涉及到网络请求的时候，网络传输的时间可能远大于请求的处理时间，因此合并网络请求也很有必要。上层协议呢，端到端对话次数尽量不要太频繁（Chatty），否则的话，总的应用层吞吐量不会很高。&lt;/p&gt; &lt;h1&gt;五、更先进算法和数据结构&lt;/h1&gt; &lt;p&gt;优化策略中的最后一个大类就是“更先进算法和数据结构”。这两个策略是紧密配合的，比如先进的算法有时候会需要先进的数据结构；而且它们往往和程序的设计代码直接相关，所以放在一起。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;9. 先进的算法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;同一个问题，肯定会有不同的算法实现，进而就会有不同的性能。比如各种排序算法，就是各有千秋。有的实现可能是时间换空间，有的实现可能是空间换时间，那么就需要根据你自己的实际情况做权衡。&lt;/p&gt; &lt;p&gt;对每一种具体的场景（包括输入集合大小、时间空间的要求、数据的大小分布等），总会有一种算法是最适合的。我们需要考虑实际情况，来选择这一最优的算法。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;10. 高效的数据结构&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;和算法的情况类似，不同的数据结构的特性，也是千差万别。&lt;/p&gt; &lt;p&gt;没有一个数据结构是在所有情况下都是最好的，比如你可能经常用到的 Java 里面列表的各种实现，包括各种口味的 List、Vector、LinkedList，它们孰优孰劣，取决于很多个指标：添加元素、删除元素、查询元素、遍历耗时等等。我们同样要权衡取舍，找出实际场合下最适合的高效的数据结构。&lt;/p&gt; &lt;h1&gt;六、总结&lt;/h1&gt; &lt;p&gt;各种性能问题的解决，需要采用一些策略；而且不同的人和不同的场景中，会采用有时相同有时迥异的策略，恰如韩愈所说的“草树知春不久归，百般红紫斗芳菲”。但花草树木争奇斗艳，说到底是因为“知春不久归”。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/preload.jpg" alt="preload" title="preload" /&gt;&lt;/p&gt; &lt;p&gt;同样的道理，这些性能优化策略，有时候很容易想到，有时候并不是那么直观。所以，&lt;strong&gt;我们需要系统地有层次地思考，而这一讲就是帮助你建立这样的思路&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;通过总结十大策略，希望你可以多从不同角度，思考同一个问题；有时候一个问题看似无解，但多方位思考，可能会突然发现非常好的解决方案。&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 04 May 2022 11:09:00 GMT</pubDate>
    </item>
    <item>
      <title>解决宿主机MAC不能访问虚拟机中CENTOS的TOMCAT服务器</title>
      <link>https://maruifu.cn/article/237</link>
      <content:encoded>&lt;h2&gt;情况描述&lt;/h2&gt; &lt;p&gt;虚拟机中的系统为CentOS，充当服务器，但是开启Tomcat后，在宿主机Mac中无法访问，显示请求被拒接，如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/2022-05-04-6.45.15.png" alt="截屏2022-05-04 下午6.45.15" title="截屏2022-05-04 下午6.45.15" /&gt;&lt;/p&gt; &lt;p&gt;除此之外，但是&lt;strong&gt;可以使用ssh，也可以ping通&lt;/strong&gt;。&lt;/p&gt; &lt;h2&gt;分析&lt;/h2&gt; &lt;p&gt;初步认为就是防火墙的问题，但是参考iptables的一些停用方法，直接显示没有iptables这个服务；后面想验证到底是宿主机还是虚拟机的问题，在5000端口，跑了一个简单的Flask服务器（在虚拟机中可通过本机ip地址+端口号进行访问），在宿主机中仍然无法访问，同时也通过其他的一些设备来访问相应的服务器，都无法访问，从这里看来，问题还是出在了虚拟机中；后面又在宿主机mac中开启了一个服务器，在虚拟机和其它局域网设备中都可以访问，因此&lt;strong&gt;断定还是虚拟机的问题&lt;/strong&gt;。那么，没有安装iptables的CentOS，究竟是出了什么问题呢？&lt;/p&gt; &lt;h2&gt;解决办法&lt;/h2&gt; &lt;p&gt;因为我使用的是&lt;strong&gt;CentOS 7&lt;/strong&gt;，使用iptables的版本是7以前的，CentOS 7使用firewall作为防火墙。&lt;/p&gt; &lt;p&gt;查看已经开放的端口：&lt;code&gt;firewall-cmd --list-ports&lt;/code&gt; 开启端口：&lt;code&gt;firewall-cmd --zone=public --add-port=80/tcp --permanent&lt;/code&gt;&lt;/p&gt; &lt;p&gt;命令含义：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;–zone #作用域 –add-port=80/tcp #添加端口，格式为：端口/通讯协议 –permanent #永久生效，没有此参数重启后失效123 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;重启、停止、禁用、查看防火墙&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;firewall-cmd --reload #重启firewall systemctl stop firewalld.service #停止firewall systemctl disable firewalld.service #禁止firewall开机启动 firewall-cmd --state #查看默认防火墙状态（关闭后显示notrunning，开启后显示running）1234 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;因此结合上述命令来看，需要将8080端口添加到防火墙的开放端口中，然后重新载入防火墙的配置即可。如下：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent sudo firewall-cmd --reload sudo firewall-cmd --list-ports123 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最后面来一张成功访问的截图：&lt;img src="https://img.maruifu.com/images/2022/05/04/2022-05-04-6.46.22.png" alt="截屏2022-05-04 下午6.46.22" title="截屏2022-05-04 下午6.46.22" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 04 May 2022 10:47:02 GMT</pubDate>
    </item>
    <item>
      <title>CentOS 7安装 ifconfig 管理命令</title>
      <link>https://maruifu.cn/article/236</link>
      <content:encoded>&lt;h4&gt;1. 安装的需求背景&lt;/h4&gt; &lt;p&gt;我们知道ifconfig 命令可以用于查看、配置、启用或禁用指定网络接口，如配置网卡的IP地址、掩码、广播地址、网关等，功能不可谓不丰富。&lt;/p&gt; &lt;p&gt;此命令的功能和Wndows系统的ipconfig非常类似。&lt;/p&gt; &lt;p&gt;但是，Centos7 默认已不再安装此命令，其中很多功能用 ip addr 指令 替代了。&lt;/p&gt; &lt;p&gt;考虑到 既有的很多管理工具或脚本都调用了此功能命令（ifconfig），如果将这些工具直接迁移过来会报错，如果对这个指令用其它指令进行替换，及对这些工具升级，则增加了工作量，还增加了出错的风险。&lt;/p&gt; &lt;p&gt;所以，在CentOS 7 系统中 安装 ifconfig 命令很有必要。&lt;/p&gt; &lt;h4&gt;2. 测试安装的具体步骤&lt;/h4&gt; &lt;p&gt;CentOS 7 系统默认 没有安装 ifconfig 命令。&lt;/p&gt; &lt;p&gt;如果直接运行 &lt;strong&gt;ifconfig&lt;/strong&gt; 命令，则提示错误 ：-bash: ifconfig: command not found&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563191.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;此时，查看 /sbin 目录下，其实是没有 ifconfig 文件的。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563192.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;那么 如何安装ifconfig 呢？我们首先想到的是 运行 &lt;strong&gt;yum install ifconfig&lt;/strong&gt; 。执行效果如何呢？&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563193.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;结果是：&lt;/p&gt; &lt;p&gt;No package ifconfig available. Error: Nothing to do&lt;/p&gt; &lt;p&gt;啊？！ 没有 ifconfig 安装包，是不是 我们就要放弃了呢？&lt;/p&gt; &lt;p&gt;其实，我们 还可以通过yum 命令的search选项 来对 包 （package）进行再次搜索。&lt;/p&gt; &lt;p&gt;&lt;em&gt;search：可以搜寻某个软件名称或者是描述（description）的重要关键字。此指令可以查找显示出相关的软件有哪些。&lt;/em&gt;&lt;/p&gt; &lt;p&gt;所以，在放弃前，我们运行以下命令：&lt;/p&gt; &lt;p&gt;&lt;em&gt;&lt;strong&gt;yum search ifconfig&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt; &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563194.png" alt="img" title="img" /&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt; &lt;p&gt;以上运行结果，我们只要分析最好一行就可以。&lt;strong&gt;Matched:&lt;/strong&gt; ifconfig 这个 分割行 是用来显示 匹配结果的。&lt;/p&gt; &lt;p&gt;最后一行 中 冒号（：）前面的数据， （&lt;strong&gt;net-tools.x86_64&lt;/strong&gt; ） 是匹配的软件包；冒号（：）后面的数据，（&lt;strong&gt;Basic networking tools&lt;/strong&gt; ） 是对前面包的描述。&lt;/p&gt; &lt;p&gt;结合上面的信息，即 通过运行 &lt;em&gt;&lt;strong&gt;yum search ifconfig&lt;/strong&gt;&lt;/em&gt; 提示我们： 安装ifconfig 包 只需要安装 net-tools.x86_64 即可。&lt;/p&gt; &lt;p&gt;所以，我们执行 &lt;strong&gt;yum install net-tools.x86_64&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563195.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;安装后，ifconfig 命令可以正常执行。因含有ip敏感信息，截图省略。&lt;/p&gt; &lt;p&gt;并且，查看/sbin 目录，此时 ifconfig 文件也出现了。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/05/04/181011090563196.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3. 总结&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;CentOS 7 安装 ifconfig 管理命令，通过yum 安装，运行 yum install net-tools.x86_64 即可安装。&lt;/strong&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 04 May 2022 10:41:01 GMT</pubDate>
    </item>
    <item>
      <title>如何关闭常见浏览器的 HSTS 功能</title>
      <link>https://maruifu.cn/article/235</link>
      <content:encoded>&lt;p&gt;在安装配置 SSL 证书时，可以使用一种能使数据传输更加安全的Web安全协议，即在服务器端上开启&lt;code&gt;HSTS&lt;/code&gt; (HTTP Strict Transport Security)。它告诉浏览器只能通过HTTPS访问，而绝对禁止HTTP方式。&lt;img src="https://img.maruifu.com/images/2022/05/04/64-20220504182703149.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;HTTP Strict Transport Security (HSTS) is an opt-in security enhancement that is specified by a web application through the use of a special response header. Once a supported browser receives this header that browser will prevent any communications from being sent over HTTP to the specified domain and will instead send all communications over HTTPS. It also prevents HTTPS click through prompts on browsers.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;但是，在日常开发的过程中，有时我们会想测试页面在 HTTP 连接中的表现情况，这时 HSTS 的存在会让调试不能方便的进行下去。而且由于 HSTS 并不是像 cookie 一样存放在浏览器缓存里，简单的清空浏览器缓存操作并没有什么效果，页面依然通过 HTTPS 的方式传输。&lt;img src="https://img.maruifu.com/images/2022/05/04/63.png" alt="img" title="img" /&gt; 那么怎样才能关闭浏览器的 HSTS 呢，各种谷歌~~度娘~~之后，在这里汇总一下几大常见浏览器 HSTS 的关闭方法。&lt;/p&gt; &lt;h2&gt;Safari 浏览器&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;完全关闭 Safari&lt;/li&gt; &lt;li&gt;删除 &lt;code&gt;~/Library/Cookies/HSTS.plist&lt;/code&gt; 这个文件&lt;/li&gt; &lt;li&gt;重新打开 Safari 即可&lt;/li&gt; &lt;li&gt;极少数情况下，需要重启系统&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;Chrome 浏览器&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;地址栏中输入 &lt;code&gt;chrome://net-internals/#hsts&lt;/code&gt;&lt;/li&gt; &lt;li&gt;在 &lt;code&gt;Delete domain&lt;/code&gt; 中输入项目的域名，并&lt;code&gt;Delete&lt;/code&gt; 删除&lt;/li&gt; &lt;li&gt;可以在 &lt;code&gt;Query domain&lt;/code&gt; 测试是否删除成功&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;Opera 浏览器&lt;/h2&gt; &lt;p&gt;和 Chrome 方法一样&lt;/p&gt; &lt;h2&gt;Firefox 浏览器&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;关闭所有已打开的页面&lt;/li&gt; &lt;li&gt;清空历史记录和缓存&lt;/li&gt; &lt;li&gt;地址栏输入&lt;code&gt;about:permissions&lt;/code&gt;&lt;/li&gt; &lt;li&gt;搜索项目域名，并点击 &lt;code&gt;Forget About This Site&lt;/code&gt;&lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Wed, 04 May 2022 10:27:50 GMT</pubDate>
    </item>
    <item>
      <title>Redis 6.0.8 集群搭建(三主三从)</title>
      <link>https://maruifu.cn/article/234</link>
      <content:encoded>&lt;h2&gt;下载与安装&lt;/h2&gt; &lt;p&gt;首先从 Redis 官网下载 Redis 源代码并解压，这里使用的是 redis-6.0.8.tar.gz 版本&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 创建目录 mkdir /usr/local/redis/redis-cluster # 进入目录 cd /usr/local/redis/redis-cluster # 下载安装包 wget http://download.redis.io/releases/redis-6.0.8.tar.gz # 解压 tar -zxzf redis-6.0.8.tar.gz #进入目录  cd  redis-6.0.8 # 编译用 如果不安装会编译报错  yum -y install gcc-c++  #加 MALLOC=libc 为了防止编译出错，具体百度 make MALLOC=libc make MALLOC=libc make install PREFIX=/apprun/redis # 目标安装路径 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建数据&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#创建目录 cd /usr/local/redis/redis-cluster mkdir logs conf data pid #创建数据目录 cd data mkdir data1111 data2222 data3333 data4444 data5555 data6666 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改配置文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;cd /usr/local/redis/redis-cluster/conf cp /usr/local/redis/redis-cluster/redis/redis.conf . mv redis.conf redis1111.conf vim redis1111.conf &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# 修改绑定地址 bind 192.168.5.199 protected-mode yes # 修改端口号,但集群状态下需要注意,集群使用端口会在此端口上增加10000,所以redis自身端口+10000不能超过65535 port 1111 tcp-backlog 511 timeout 0 tcp-keepalive 300 # Redis是否后台运行 daemonize yes supervised no # 进程文件存储地址 pidfile /usr/local/redis/redis-cluster/pid/redis1111.pid loglevel notice # 日志文件存储地址 logfile &amp;quot;/usr/local/redis/redis-cluster/logs/redis1111.log&amp;quot; databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes   # 持久化错误之后，停止写入Redis rdbcompression yes   # 快照是否压缩存储（压缩消耗CPU、占用空间小） rdbchecksum yes   # 存储的快照进行数据校验（增加约10%的性能消耗） dbfilename dump.rdb rdb-del-sync-files no # 数据存放目录 dir /usr/local/redis/redis-cluster/data/data1111  # 主要是针对master对应的slave节点设置的，在slave节点数据同步的时候用到 masterauth redis123 replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-diskless-load disabled repl-disable-tcp-nodelay no replica-priority 100 acllog-max-len 128 # 对登录权限做限制，redis每个节点的requirepass可以是独立、不同的 requirepass redis123 lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no lazyfree-lazy-user-del no oom-score-adj no oom-score-adj-values 0 200 800 # aof日志开启，有需要就开启 appendonly yes appendfilename &amp;quot;appendonly.aof&amp;quot; appendfsync everysec # 每秒同步一次（always 每次修改同步一次） no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 # 开启集群模式 cluster-enabled yes # 集群的配置文件，首次启动自动生成 cluster-config-file /usr/local/redis/redis-cluster/conf/nodes1111.conf  # 请求超时时间，默认15s cluster-node-timeout 10000 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events &amp;quot;&amp;quot; hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;复制配置&lt;/h2&gt; &lt;p&gt;Redis 集群一般由多个节点组成，节点数量至少为 6 个，才能保证组成完整高可用的集群，所以再复制 5 份配置文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp redis1111.conf redis2222.conf cp redis1111.conf redis3333.conf cp redis1111.conf redis4444.conf cp redis1111.conf redis5555.conf cp redis1111.conf redis6666.conf  # vim 进去里边修改：比如：% s/1111/2222/ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置启动命令及启动集群&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;cd /usr/local/redis/redis-cluster/redis/src # 这样可以直接使用该命令，不需要再进入该命令目录 cp redis-cli redis-server /usr/bin/  #编写统一启动脚本、启动 cd /usr/local/redis/redis-cluster vi redis-start.sh  #!/bin/bash redis-server /usr/local/redis/redis-cluster/conf/redis1111.conf redis-server /usr/local/redis/redis-cluster/conf/redis2222.conf redis-server /usr/local/redis/redis-cluster/conf/redis3333.conf redis-server /usr/local/redis/redis-cluster/conf/redis4444.conf redis-server /usr/local/redis/redis-cluster/conf/redis5555.conf redis-server /usr/local/redis/redis-cluster/conf/redis6666.conf  # 赋予权限 chmod 755 redis-start.sh ./redis-start.sh #启动集群 执行命令：redis-cli --cluster create 192.168.5.199:1111 192.168.5.199:2222 192.168.5.199:3333 192.168.5.199:4444 192.168.5.199:5555 192.168.5.199:6666 --cluster-replicas 1 -a redis123 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;参数说明：注意 redis-5.0.0 版本开始才支持 “ --cluster ”&lt;/p&gt; &lt;p&gt;create 表示创建一个redis集群。&lt;/p&gt; &lt;p&gt;--cluster-replicas 1 表示为集群中的每一个主节点指定一个从节点，即一比一的复制。&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. &amp;gt;&amp;gt;&amp;gt; Performing hash slots allocation on 6 nodes... Master[0] -&amp;gt; Slots 0 - 5460 Master[1] -&amp;gt; Slots 5461 - 10922 Master[2] -&amp;gt; Slots 10923 - 16383 Adding replica 192.168.5.199:5555 to 192.168.5.199:1111 Adding replica 192.168.5.199:6666 to 192.168.5.199:2222 Adding replica 192.168.5.199:4444 to 192.168.5.199:3333 &amp;gt;&amp;gt;&amp;gt; Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 3c09b846d56efc9c3d3367b7db74317ff0c54980 192.168.5.199:1111    slots:[0-5460] (5461 slots) master M: f35e954bd158848b5dc09358752daf7db35c7d7e 192.168.5.199:2222    slots:[5461-10922] (5462 slots) master M: 2e54db41b7916b64d7689b22a0f456dd099086fc 192.168.5.199:3333    slots:[10923-16383] (5461 slots) master S: edf74be3f4784f0d560505ad0ca10bcd76d57d42 192.168.5.199:4444    replicates 3c09b846d56efc9c3d3367b7db74317ff0c54980 S: 609822d35d216b3bea5a308f25fa537055d85089 192.168.5.199:5555    replicates f35e954bd158848b5dc09358752daf7db35c7d7e S: ee9838644056c360a88399b3acb5f905e17eebc5 192.168.5.199:6666    replicates 2e54db41b7916b64d7689b22a0f456dd099086fc Can I set the above configuration? (type 'yes' to accept): yes &amp;gt;&amp;gt;&amp;gt; Nodes configuration updated &amp;gt;&amp;gt;&amp;gt; Assign a different config epoch to each node &amp;gt;&amp;gt;&amp;gt; Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join . &amp;gt;&amp;gt;&amp;gt; Performing Cluster Check (using node 192.168.5.199:1111) M: 3c09b846d56efc9c3d3367b7db74317ff0c54980 192.168.5.199:1111    slots:[0-5460] (5461 slots) master    1 additional replica(s) S: edf74be3f4784f0d560505ad0ca10bcd76d57d42 192.168.5.199:4444    slots: (0 slots) slave    replicates 3c09b846d56efc9c3d3367b7db74317ff0c54980 S: ee9838644056c360a88399b3acb5f905e17eebc5 192.168.5.199:6666    slots: (0 slots) slave    replicates 2e54db41b7916b64d7689b22a0f456dd099086fc M: 2e54db41b7916b64d7689b22a0f456dd099086fc 192.168.5.199:3333    slots:[10923-16383] (5461 slots) master    1 additional replica(s) S: 609822d35d216b3bea5a308f25fa537055d85089 192.168.5.199:5555    slots: (0 slots) slave    replicates f35e954bd158848b5dc09358752daf7db35c7d7e M: f35e954bd158848b5dc09358752daf7db35c7d7e 192.168.5.199:2222    slots:[5461-10922] (5462 slots) master    1 additional replica(s) [OK] All nodes agree about slots configuration. &amp;gt;&amp;gt;&amp;gt; Check for open slots... &amp;gt;&amp;gt;&amp;gt; Check slots coverage... [OK] All 16384 slots covered.  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;检测集群完整性&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#执行命令 redis-cli --cluster check 192.168.121.128:1111 -a redis123 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;[root@localhost ~]# redis-cli --cluster check 192.168.5.199:1111 -a &amp;quot;redis123&amp;quot; Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 192.168.5.199:1111 (3c09b846...) -&amp;gt; 0 keys | 5461 slots | 1 slaves. 192.168.5.199:3333 (2e54db41...) -&amp;gt; 1 keys | 5461 slots | 1 slaves. 192.168.5.199:2222 (f35e954b...) -&amp;gt; 1 keys | 5462 slots | 1 slaves. [OK] 2 keys in 3 masters. 0.00 keys per slot on average. &amp;gt;&amp;gt;&amp;gt; Performing Cluster Check (using node 192.168.5.199:1111) M: 3c09b846d56efc9c3d3367b7db74317ff0c54980 192.168.5.199:1111    slots:[0-5460] (5461 slots) master    1 additional replica(s) S: edf74be3f4784f0d560505ad0ca10bcd76d57d42 192.168.5.199:4444    slots: (0 slots) slave    replicates 3c09b846d56efc9c3d3367b7db74317ff0c54980 S: ee9838644056c360a88399b3acb5f905e17eebc5 192.168.5.199:6666    slots: (0 slots) slave    replicates 2e54db41b7916b64d7689b22a0f456dd099086fc M: 2e54db41b7916b64d7689b22a0f456dd099086fc 192.168.5.199:3333    slots:[10923-16383] (5461 slots) master    1 additional replica(s) S: 609822d35d216b3bea5a308f25fa537055d85089 192.168.5.199:5555    slots: (0 slots) slave    replicates f35e954bd158848b5dc09358752daf7db35c7d7e M: f35e954bd158848b5dc09358752daf7db35c7d7e 192.168.5.199:2222    slots:[5461-10922] (5462 slots) master    1 additional replica(s) [OK] All nodes agree about slots configuration. &amp;gt;&amp;gt;&amp;gt; Check for open slots... &amp;gt;&amp;gt;&amp;gt; Check slots coverage... [OK] All 16384 slots covered.  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;登录验证&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#执行命令 redis-cli -h 192.168.5.199 -p 1111 -a &amp;quot;redis123&amp;quot; Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 192.168.5.199:1111&amp;gt; info cluster # Cluster cluster_enabled:1 192.168.5.199:1111&amp;gt; cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:67776 cluster_stats_messages_pong_sent:71182 cluster_stats_messages_sent:138958 cluster_stats_messages_ping_received:71177 cluster_stats_messages_pong_received:67776 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:138958 192.168.5.199:1111&amp;gt; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 22 Apr 2022 04:01:00 GMT</pubDate>
    </item>
    <item>
      <title>批量下载wallhaven壁纸</title>
      <link>https://maruifu.cn/article/233</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;#!/bin/bash # # This script gets the beautiful wallpapers from http://wallhaven.cc # This script is brought to you by MacEarl and is based on the # script for wallbase.cc (https://github.com/sevensins/Wallbase-Downloader) # # This Script is written for GNU Linux, it should work under Mac OS  REVISION=0.2.6  ##################################### ###   Needed for NSFW/Favorites   ### ##################################### # Enter your API key # you can get it here: https://wallhaven.cc/settings/account APIKEY=&amp;quot;beYBHQ89MncZeTt96JXRyrqmwBFifWUr&amp;quot; ##################################### ### End needed for NSFW/Favorites ### #####################################  ##################################### ###     Configuration Options     ### ##################################### # Where should the Wallpapers be stored? LOCATION=/Volumes/photo # How many Wallpapers should be downloaded, should be multiples of the # value in the THUMBS Variable WPNUMBER=48 # What page to start downloading at, default and minimum of 1. STARTPAGE=1 # Type standard (newest, oldest, random, hits, mostfav), search, collections # (for now only the default collection), useruploads (if selected, only # FILTER variable will change the outcome) TYPE=standard # From which Categories should Wallpapers be downloaded, first number is # for General, second for Anime, third for People, 1 to enable category, # 0 to disable it CATEGORIES=110 # filter wallpapers before downloading, first number is for sfw content, # second for sketchy content, third for nsfw content, 1 to enable, # 0 to disable FILTER=111 # Which Resolutions should be downloaded, leave empty for all (most common # resolutions possible, for details see wallhaven site), separate multiple # resolutions with , eg. 1920x1080,1920x1200 RESOLUTION= # alternatively specify a minimum resolution, please note that specifying # both resolutions and a minimum resolution will result in the desired # resolutions being ignored, to avoid unwanted behavior only set one of the # two options and leave the other blank ATLEAST= # Which aspectratios should be downloaded, leave empty for all (possible # values: 4x3, 5x4, 16x9, 16x10, 21x9, 32x9, 48x9, 9x16, 10x16), separate mutliple ratios # with , eg. 4x3,16x9 ASPECTRATIO= # Which Type should be displayed (relevance, random, date_added, views, # favorites, toplist, toplist-beta) MODE=date_added # if MODE is set to toplist show the toplist for the given timeframe # possible values: 1d (last day), 3d (last 3 days), 1w (last week), # 1M (last month), 3M (last 3 months), 6M (last 6 months), 1y (last year) TOPRANGE= # How should the wallpapers be ordered (desc, asc) ORDER=desc # Collections, only used if TYPE = collections # specify the name of the collection you want to download # Default is the default collection name on wallhaven # If you want to download your own Collections make sure USR is set to your username # If you want to download someone elses public collection enter the name here # and the username under USR # Please note that the only filter option applied to Collections is the Number # of Wallpapers to download, there is no filter for resolution, purity, ... COLLECTION=&amp;quot;Default&amp;quot; # Searchterm, only used if TYPE = search # you can also search by tags, use id:TAGID # to get the tag id take a look at: https://wallhaven.cc/tags/ # for example: to search for nature related wallpapers via the nature tag # instead of the keyword use QUERY=&amp;quot;id:37&amp;quot; QUERY=&amp;quot;nature&amp;quot; # Search images containing color # values are RGB (000000 = black, ffffff = white, ff0000 = red, ...) COLOR=&amp;quot;&amp;quot; # Should the search results be saved to a separate subfolder? # 0 for no separate folder, 1 for separate subfolder SUBFOLDER=0 # User from which wallpapers should be downloaded # used for TYPE=useruploads and TYPE=collections # If you want to download your own Collection this has to be set to your username USR=&amp;quot;AksumkA&amp;quot; # use gnu parallel to speed up the download (0, 1), if set to 1 make sure # you have gnuparallel installed, see normal.vs.parallel.txt for # speed improvements # using this option can lead to cloudflare blocking some of the downloads PARALLEL=0 # custom thumbnails per page # changeable here: https://wallhaven.cc/settings/browsing # valid values: 24, 32, 64 # if set to 32 or 64 you need to provide an api key THUMBS=24 ##################################### ###   End Configuration Options   ### #####################################  function checkDependencies {     printf &amp;quot;Checking dependencies...&amp;quot;     dependencies=(wget jq sed)     [[ $PARALLEL == 1 ]] &amp;amp;&amp;amp; dependencies+=(parallel)      for name in &amp;quot;${dependencies[@]}&amp;quot;     do         [[ $(command -v &amp;quot;$name&amp;quot; 2&amp;gt;/dev/null) ]] ||         { printf &amp;quot;\n%s needs to be installed. Use your package manager to do so, e.g. 'sudo apt install %s'&amp;quot; &amp;quot;$name&amp;quot; &amp;quot;$name&amp;quot;;deps=1; }     done      if [[ $deps -ne 1 ]]     then         printf &amp;quot;OK\n&amp;quot;     else         printf &amp;quot;\nInstall the above and rerun this script\n&amp;quot;         exit 1     fi } # /checkDependencies  # # sets the authentication header/API key to give the user more functionality # requires 1 arguments: # arg1: API key # function setAPIkeyHeader {     # checking parameters -&amp;gt; if not ok print error and exit script     if [ $# -lt 1 ] || [ &amp;quot;$1&amp;quot; == '' ]     then         printf &amp;quot;Please make sure to enter a valid API key,\n&amp;quot;         printf &amp;quot;it is needed for NSFW Content and downloading \n&amp;quot;         printf &amp;quot;your Collections also make sure your Thumbnails per\n&amp;quot;         printf &amp;quot;Page Setting matches the THUMBS Variable\n\n&amp;quot;         printf &amp;quot;Press any key to exit\n&amp;quot;         read -r         exit     fi      # everythings ok --&amp;gt; set api key header     httpHeader=&amp;quot;X-API-Key: $APIKEY&amp;quot; } # /setAPIkeyHeader  # # downloads Page with Thumbnails # function getPage {     # checking parameters -&amp;gt; if not ok print error and exit script     if [ $# -lt 1 ]     then         printf &amp;quot;getPage expects at least 1 argument\\n&amp;quot;         printf &amp;quot;arg1:\\tparameters for the wget -q command\\n\\n&amp;quot;         printf &amp;quot;press any key to exit\\n&amp;quot;         read -r         exit     fi      # parameters ok --&amp;gt; get page     WGET -O tmp &amp;quot;https://wallhaven.cc/api/v1/$1&amp;quot; } # /getPage  # # downloads all the wallpaper from a wallpaperfile # arg1: the file containing the wallpapers # function downloadWallpapers {     if (( &amp;quot;$page&amp;quot; &amp;gt;= &amp;quot;$(jq -r &amp;quot;.meta.last_page&amp;quot; tmp)&amp;quot; ))     then         downloadEndReached=true     fi      for ((i=0; i&amp;lt;THUMBS; i++))     do         imgURL=$(jq -r &amp;quot;.data[$i].path&amp;quot; tmp)          filename=$(echo &amp;quot;$imgURL&amp;quot;| sed &amp;quot;s/.*\///&amp;quot; )         if grep -w &amp;quot;$filename&amp;quot; downloaded.txt &amp;gt;/dev/null         then             printf &amp;quot;\\tWallpaper %s already downloaded!\\n&amp;quot; &amp;quot;$imgURL&amp;quot;         elif [ $PARALLEL == 1 ]         then             echo &amp;quot;$imgURL&amp;quot; &amp;gt;&amp;gt; download.txt         else             # check if downloadWallpaper was successful             if downloadWallpaper &amp;quot;$imgURL&amp;quot;             then                 echo &amp;quot;$filename&amp;quot; &amp;gt;&amp;gt; downloaded.txt             fi         fi     done      if [ $PARALLEL == 1 ] &amp;amp;&amp;amp; [ -f ./download.txt ]     then         # export wget wrapper and download function to make it         # available for parallel         export -f WGET coolDown downloadWallpaper         # shellcheck disable=SC2016         SHELL=$(type -p bash) parallel --gnu --no-notice \             'imgURL={} &amp;amp;&amp;amp; downloadWallpaper $imgURL &amp;amp;&amp;amp; echo &amp;quot;$imgURL&amp;quot;| sed &amp;quot;s/.*\///&amp;quot; &amp;gt;&amp;gt; downloaded.txt' &amp;lt; download.txt             rm tmp download.txt         else             rm tmp     fi } # /downloadWallpapers  # # downloads a single Wallpaper by guessing its extension, this eliminates # the need to download each wallpaper page, now only the thumbnail page # needs to be downloaded # function downloadWallpaper {     if [[ &amp;quot;$1&amp;quot; != null ]]     then         WGET &amp;quot;$1&amp;quot;     else         return 1     fi } # /downloadWallpaper  # # Waits for 30 seconds if rate limiting is detected # function coolDown {     printf &amp;quot;\\t -Rate Limiting detected, sleeping for 30 seconds\\n&amp;quot;     sleep 30     WGET &amp;quot;$@&amp;quot; } # /coolDown  # # wrapper for wget with some default arguments # arg0: additional arguments for wget (optional) # arg1: file to download # function WGET {     # checking parameters -&amp;gt; if not ok print error and exit script     if [ $# -lt 1 ]     then         printf &amp;quot;WGET expects at least 1 argument\\n&amp;quot;         printf &amp;quot;arg0:\\tadditional arguments for wget (optional)\\n&amp;quot;         printf &amp;quot;arg1:\\tfile to download\\n\\n&amp;quot;         printf &amp;quot;press any key to exit\\n&amp;quot;         read -r         exit     fi      # default wget command     wget --server-response -q --header=&amp;quot;$httpHeader&amp;quot; --keep-session-cookies \          --save-cookies cookies.txt --load-cookies cookies.txt &amp;quot;$@&amp;quot; 2&amp;gt;&amp;amp;1 | \          grep &amp;quot;429 Too Many Requests&amp;quot; &amp;gt;/dev/null &amp;amp;&amp;amp; coolDown &amp;quot;$@&amp;quot;      return &amp;quot;${PIPESTATUS[0]}&amp;quot; } # /WGET  # # displays help text (valid command line arguments) # function helpText {     printf &amp;quot;Usage: ./wallhaven.sh [OPTIONS]\\n&amp;quot;     printf &amp;quot;Download wallpapers from wallhaven.cc\\n\\n&amp;quot;     printf &amp;quot;If no options are specified, default values from within the &amp;quot;     printf &amp;quot;script will be used\\n\\n&amp;quot;     printf &amp;quot; -l, --location\\t\\tlocation where the wallpapers will be &amp;quot;     printf &amp;quot;stored\\n&amp;quot;     printf &amp;quot; -n, --number\\t\\tNumber of Wallpapers to download\\n&amp;quot;     printf &amp;quot; -s, --startpage\\tpage to start downloading from\\n&amp;quot;     printf &amp;quot; -t, --type\\t\\tType of download Operation: standard, search, &amp;quot;     printf &amp;quot;\\n\\t\\t\\tcollections, useruploads\\n&amp;quot;     printf &amp;quot; -c, --categories\\tcategories to download from, eg. 111 for &amp;quot;     printf &amp;quot;General,\\n\\t\\t\\tAnime and People, 1 to include, 0 to exclude\\n&amp;quot;     printf &amp;quot; -f, --filter\\t\\tfilter out content based on purity rating, &amp;quot;     printf &amp;quot;eg. 111 \\n\\t\\t\\tfor SFW, sketchy and NSFW content, 1 to &amp;quot;     printf &amp;quot;include, \\n\\t\\t\\t0 to exclude\\n&amp;quot;     printf &amp;quot; -r, --resolution\\tresolutions to download, separate mutliple&amp;quot;     printf &amp;quot; \\n\\t\\t\\tresolutions by ,\\n&amp;quot;     printf &amp;quot; -g, --atleast\\t\\tminimum resolution, show all images with a&amp;quot;     printf &amp;quot;\\n\\t\\t\\tresolution greater than the specified value&amp;quot;     printf &amp;quot;\\n\\t\\t\\tdo not use in combination with -r (--resolution)\\n&amp;quot;     printf &amp;quot; -a, --aspectratio\\tonly download wallpaper with given &amp;quot;     printf &amp;quot;aspectratios, \\n\\t\\t\\tseparate multiple aspectratios by ,\\n&amp;quot;     printf &amp;quot; -m, --mode\\t\\tsorting mode for wallpapers: relevance, random&amp;quot;     printf &amp;quot;,\\n\\t\\t\\tdate_added, views, favorites \\n&amp;quot;     printf &amp;quot; -o, --order\\t\\torder ascending (asc) or descending &amp;quot;     printf &amp;quot;(desc)\\n&amp;quot;     printf &amp;quot; -b, --collection\\tname of the collections to download\\n&amp;quot;     printf &amp;quot; -q, --query\\t\\tsearch query, eg. 'mario', single &amp;quot;     printf &amp;quot;quotes needed,\\n\\t\\t\\tfor searching exact phrases use double &amp;quot;     printf &amp;quot;quotes \\n\\t\\t\\tinside single quotes, eg. '\&amp;quot;super mario\&amp;quot;'&amp;quot;     printf &amp;quot;\\n&amp;quot;     printf &amp;quot; -d, --dye, --color\\tsearch for wallpapers containing the &amp;quot;     printf &amp;quot;given color,\\n&amp;quot;     printf &amp;quot;\\t\\t\\tcolor values are RGB without a leading #\\n&amp;quot;     printf &amp;quot; -u, --user\\t\\tdownload wallpapers from given user\\n&amp;quot;     printf &amp;quot; -p, --parallel\\t\\tmake use of gnu parallel (1 to enable, 0 &amp;quot;     printf &amp;quot;to disable)\\n&amp;quot;     printf &amp;quot; -v, --version\\t\\tshow current version\\n&amp;quot;     printf &amp;quot; -h, --help\\t\\tshow this help text and exit\\n\\n&amp;quot;     printf &amp;quot;Examples:\\n&amp;quot;     printf &amp;quot;./wallhaven.sh\\t-l ~/wp/ -n 48 -s 1 -t standard -c 101 -f 111&amp;quot;     printf &amp;quot; -r 1920x1080 \\n\\t\\t-a 16x9 -m random -o desc -p 1\\n\\n&amp;quot;     printf &amp;quot;Download 48 random wallpapers with a resolution of 1920x1080 &amp;quot;     printf &amp;quot;and \\nan aspectratio of 16x9 to ~/wp/ starting with page 1 &amp;quot;     printf &amp;quot;from the \\ncategories general and people including SFW, sketchy&amp;quot;     printf &amp;quot; and NSWF Content\\nwhile utilizing gnu parallel\\n\\n&amp;quot;     printf &amp;quot;./wallhaven.sh\\t-l ~/wp/ -n 48 -s 1 -t search -c 111 -f 100 -r &amp;quot;     printf &amp;quot;1920x1080 -a 16x9\\n\\t\\t-m relevance -o desc -q &amp;quot;     printf &amp;quot;'\&amp;quot;super mario\&amp;quot;' -d cc0000 -p 1\\n\\n&amp;quot;     printf &amp;quot;Download 48 wallpapers related to the search query &amp;quot;     printf &amp;quot;\&amp;quot;super mario\&amp;quot; containing \\nthe color #cc0000 with a resolution&amp;quot;     printf &amp;quot; of 1920x1080 and an aspectratio of 16x9\\nto ~/wp/ starting &amp;quot;     printf &amp;quot;with page 1 from the categories general, anime and people,\\n&amp;quot;     printf &amp;quot;including SFW Content and excluding sketchy and NSWF Content &amp;quot;     printf &amp;quot;while utilizing\\ngnu parallel\\n\\n\\n&amp;quot;     printf &amp;quot;latest version available at: &amp;quot;     printf &amp;quot;&amp;lt;https://github.com/macearl/Wallhaven-Downloader&amp;gt;\\n&amp;quot; } # /helptext  # Command line Arguments while [[ $# -ge 1 ]]     do     key=&amp;quot;$1&amp;quot;      case $key in         -l|--location)             LOCATION=&amp;quot;$2&amp;quot;             shift;;         -n|--number)             WPNUMBER=&amp;quot;$2&amp;quot;             shift;;         -s|--startpage)             STARTPAGE=&amp;quot;$2&amp;quot;             shift;;         -t|--type)             TYPE=&amp;quot;$2&amp;quot;             shift;;         -c|--categories)             CATEGORIES=&amp;quot;$2&amp;quot;             shift;;         -f|--filter)             FILTER=&amp;quot;$2&amp;quot;             shift;;         -r|--resolution)             RESOLUTION=&amp;quot;$2&amp;quot;             shift;;         -g|--atleast)             ATLEAST=&amp;quot;$2&amp;quot;             shift;;         -a|--aspectratio)             ASPECTRATIO=&amp;quot;$2&amp;quot;             shift;;         -m|--mode)             MODE=&amp;quot;$2&amp;quot;             shift;;         -o|--order)             ORDER=&amp;quot;$2&amp;quot;             shift;;         -b|--collection)             COLLECTION=&amp;quot;$2&amp;quot;             shift;;         -q|--query)             QUERY=${2//\'/}             shift;;         -d|--dye|--color)             COLOR=&amp;quot;$2&amp;quot;             shift;;         -u|--user)             USR=&amp;quot;$2&amp;quot;             shift;;         -p|--parallel)             PARALLEL=&amp;quot;$2&amp;quot;             shift;;         -h|--help)             helpText             exit             ;;         -v|--version)             printf &amp;quot;Wallhaven Downloader %s\\n&amp;quot; &amp;quot;$REVISION&amp;quot;             exit             ;;         *)             printf &amp;quot;unknown option: %s\\n&amp;quot; &amp;quot;$1&amp;quot;             helpText             exit             ;;     esac     shift # past argument or value     done  checkDependencies  # optionally create a separate subfolder for each search query # might download duplicates as each search query has its own list of # downloaded wallpapers if [ &amp;quot;$TYPE&amp;quot; == search ] &amp;amp;&amp;amp; [ &amp;quot;$SUBFOLDER&amp;quot; == 1 ] then     LOCATION+=/$(echo &amp;quot;$QUERY&amp;quot; | sed -e &amp;quot;s/ /_/g&amp;quot; -e &amp;quot;s/+/_/g&amp;quot; -e  &amp;quot;s/\\//_/g&amp;quot;) fi  # creates Location folder if it does not exist if [ ! -d &amp;quot;$LOCATION&amp;quot; ] then     mkdir -p &amp;quot;$LOCATION&amp;quot; fi  cd &amp;quot;$LOCATION&amp;quot; || exit  # creates downloaded.txt if it does not exist if [ ! -f ./downloaded.txt ] then     touch downloaded.txt fi  # set auth header only when it is required ( for example to download your # own collections or nsfw content... ) if  [ &amp;quot;$FILTER&amp;quot; == 001 ] || [ &amp;quot;$FILTER&amp;quot; == 011 ] || [ &amp;quot;$FILTER&amp;quot; == 111 ] \     || [ &amp;quot;$TYPE&amp;quot; == collections ] || [ &amp;quot;$THUMBS&amp;quot; != 24 ] then     setAPIkeyHeader &amp;quot;$APIKEY&amp;quot; fi  if [ &amp;quot;$TYPE&amp;quot; == standard ] then     for ((  count=0, page=&amp;quot;$STARTPAGE&amp;quot;;             count&amp;lt; &amp;quot;$WPNUMBER&amp;quot;;             count=count+&amp;quot;$THUMBS&amp;quot;, page=page+1 ));     do         printf &amp;quot;Download Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         s1=&amp;quot;search?page=$page&amp;amp;categories=$CATEGORIES&amp;amp;purity=$FILTER&amp;amp;&amp;quot;         s1+=&amp;quot;atleast=$ATLEAST&amp;amp;resolutions=$RESOLUTION&amp;amp;ratios=$ASPECTRATIO&amp;quot;         s1+=&amp;quot;&amp;amp;sorting=$MODE&amp;amp;order=$ORDER&amp;amp;topRange=$TOPRANGE&amp;amp;colors=$COLOR&amp;quot;         getPage &amp;quot;$s1&amp;quot;         printf &amp;quot;\\t- done!\\n&amp;quot;         printf &amp;quot;Download Wallpapers from Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         downloadWallpapers         printf &amp;quot;\\t- done!\\n&amp;quot;         if [ &amp;quot;$downloadEndReached&amp;quot; = true ]         then             break         fi     done  elif [ &amp;quot;$TYPE&amp;quot; == search ] || [ &amp;quot;$TYPE&amp;quot; == useruploads ] then     for ((  count=0, page=&amp;quot;$STARTPAGE&amp;quot;;             count&amp;lt; &amp;quot;$WPNUMBER&amp;quot;;             count=count+&amp;quot;$THUMBS&amp;quot;, page=page+1 ));     do         printf &amp;quot;Download Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         s1=&amp;quot;search?page=$page&amp;amp;categories=$CATEGORIES&amp;amp;purity=$FILTER&amp;amp;&amp;quot;         s1+=&amp;quot;atleast=$ATLEAST&amp;amp;resolutions=$RESOLUTION&amp;amp;ratios=$ASPECTRATIO&amp;quot;         s1+=&amp;quot;&amp;amp;sorting=$MODE&amp;amp;order=desc&amp;amp;topRange=$TOPRANGE&amp;amp;colors=$COLOR&amp;quot;         if [ &amp;quot;$TYPE&amp;quot; == search ]         then             s1+=&amp;quot;&amp;amp;q=$QUERY&amp;quot;         elif [ &amp;quot;$TYPE&amp;quot; == useruploads ]         then             s1+=&amp;quot;&amp;amp;q=@$USR&amp;quot;         fi          getPage &amp;quot;$s1&amp;quot;         printf &amp;quot;\\t- done!\\n&amp;quot;         printf &amp;quot;Download Wallpapers from Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         downloadWallpapers         printf &amp;quot;\\t- done!\\n&amp;quot;         if [ &amp;quot;$downloadEndReached&amp;quot; = true ]         then             break         fi     done  elif [ &amp;quot;$TYPE&amp;quot; == collections ] then     if [ &amp;quot;$USR&amp;quot; == &amp;quot;&amp;quot; ]     then         printf &amp;quot;Please check the value specified for USR\\n&amp;quot;         printf &amp;quot;to download a Collection it is necessary to specify a User\\n\\n&amp;quot;         printf &amp;quot;Press any key to exit\\n&amp;quot;         read -r         exit     fi      getPage &amp;quot;collections/$USR&amp;quot;       i=0     while         label=$(jq -e -r &amp;quot;.data[$i].label&amp;quot; tmp)         id=$(jq -e -r &amp;quot;.data[$i].id&amp;quot; tmp)         collectionsize=$(jq -e -r &amp;quot;.data[$i].count&amp;quot; tmp)         [[ $label != &amp;quot;$COLLECTION&amp;quot; &amp;amp;&amp;amp; $label != null ]]     do         (( i++ ))     done      if [ -z &amp;quot;$id&amp;quot; ]     then         printf &amp;quot;Please check the value specified for COLLECTION\\n&amp;quot;         printf &amp;quot;it seems that a collection with the name \&amp;quot;%s\&amp;quot; does not exist\\n\\n&amp;quot; \                 &amp;quot;$COLLECTION&amp;quot;         printf &amp;quot;Press any key to exit\\n&amp;quot;         read -r         exit     fi      for ((  count=0, page=&amp;quot;$STARTPAGE&amp;quot;;             count&amp;lt; &amp;quot;$WPNUMBER&amp;quot; &amp;amp;&amp;amp; count&amp;lt; &amp;quot;$collectionsize&amp;quot;;             count=count+&amp;quot;$THUMBS&amp;quot;, page=page+1 ));     do         printf &amp;quot;Download Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         getPage &amp;quot;collections/$USR/$id?page=$page&amp;quot;         printf &amp;quot;\\t- done!\\n&amp;quot;         printf &amp;quot;Download Wallpapers from Page %s\\n&amp;quot; &amp;quot;$page&amp;quot;         downloadWallpapers         printf &amp;quot;\\t- done!\\n&amp;quot;     done else     printf &amp;quot;error in TYPE please check Variable\\n&amp;quot; fi  rm -f cookies.txt  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 07 Apr 2022 13:47:00 GMT</pubDate>
    </item>
    <item>
      <title>常用的JVM参数，你现在就记好！</title>
      <link>https://maruifu.cn/article/232</link>
      <content:encoded>&lt;p&gt;前言&lt;/p&gt; &lt;p&gt;大家都知道，jvm在启动的时候，会执行默认的一些参数。一般情况下，这些设置的默认参数应对一些平常的项目也够用了。但是如果项目特别大了，需要增加一下堆内存的大小、或者是系统老是莫明的挂掉，想查看下gc日志来排查一下错误的原因，都需要咱们手动设置这些参数。&lt;/p&gt; &lt;h3&gt;1.verbose:gc&lt;/h3&gt; &lt;p&gt;表示，启动jvm的时候，输出jvm里面的gc信息。格式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[Full GC 178K-&amp;gt;99K(1984K)， 0.0253877 secs] &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;解读 ：Full GC 就表示执行了一次Full GC的操作，178K 和99K 就表示执行GC前内存容量和执行GC后的内存容量。1984K就表示内存总容量。后面那个是执行本次GC所消耗的时间，单位是秒。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;2.-XX:+printGC&lt;/h3&gt; &lt;p&gt;这个打印的GC信息跟上个一样，就不做介绍了。&lt;/p&gt; &lt;h3&gt;3.-XX:+PrintGCDetails&lt;/h3&gt; &lt;p&gt;打印GC的详细信息。格式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;–Heap – def new generation   total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000) –  eden space 12288K,  91% used [0x27e80000, 0x28975f20, 0x28a80000) –  from space 1536K,   0% used [0x28a80000, 0x28a80000, 0x28c00000) –  to   space 1536K,   0% used [0x28c00000, 0x28c00000, 0x28d80000) – tenured generation   total 5120K, used 0K [0x28d80000, 0x29280000, 0x34680000) –   the space 5120K,   0% used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000) – compacting perm gen  total 12288K, used 142K [0x34680000, 0x35280000, 0x38680000) –   the space 12288K,   1% used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000) –    ro space 10240K,  44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000) –    rw space 12288K,  52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000) &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;解读：new generation 就是堆内存里面的新生代。total的意思就是一共的，所以后面跟的就是新生代一共的内存大小。used也就是使用了多少内存大小。0x开头的那三个分别代表的是 底边界，当前边界，高边界。也就是新生代这片内存的起始点，当前使用到的地方和最大的内存地点。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;eden space 这个通常被翻译成伊甸园区，是在新生代里面的，一些创建的对象都会先被放进这里。后面那个12288K就表示伊甸园区一共的内存大小，91% used，很明显，表示已经使用了百分之多少。后面的那个0x跟上一行的解释一样。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;from space 和to space 是幸存者的两个区。也是属于新生代的。他两个区的大小必须是一样的。因为新生代的GC采用的是复制算法，每次只会用到一个幸存区，当一个幸存区满了的时候，把还是活的对象复制到另个幸存区，上个直接清空。这样做就不会产生内存碎片了。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;tenured generation 就表示老年代。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;compacting perm 表示永久代。由于这两个的格式跟前面我介绍的那个几乎一样，我就不必介绍了。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;4.-XX:+PrintGCTimeStamps&lt;/h3&gt; &lt;p&gt;打印GC发生的时间戳。格式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;289.556: [GC [PSYoungGen: 314113K-&amp;gt;15937K(300928K)] 405513K-&amp;gt;107901K(407680K), 0.0178568 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]  293.271: [GC [PSYoungGen: 300865K-&amp;gt;6577K(310720K)] 392829K-&amp;gt;108873K(417472K), 0.0176464 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;解读：289.556表示从jvm启动到发生垃圾回收所经历的的时间。GC表示这是新生代GC（Minor GC）。PSYoungGen表示新生代使用的是多线程垃圾回收器Parallel Scavenge。314113K-&amp;gt;15937K(300928K)]这个跟上面那个GC格式一样，只不过，这个是表示的是新生代，幸存者区。后面那个是整个堆的大小，GC前和GC后的情况。Times这个显而易见，代表GC的所消耗的时间，用户垃圾回收的时间和系统消耗的时间和最终真实的消耗时间。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;5.-X:loggc:log/gc.log&lt;/h3&gt; &lt;p&gt;这个就表示，指定输出gc.log的文件位置。（我这里写的log/gc.log就表示在当前log的目录里，把GC日志写到叫gc.log的文件里。）&lt;/p&gt; &lt;h3&gt;6.-XX:+PrintHeapAtGC&lt;/h3&gt; &lt;p&gt;表示每次GC后，都打印堆的信息。（这个打印的基本格式跟上面第二条的基本类似，我也就不比多说了。）&lt;/p&gt; &lt;h3&gt;7.-XX:+TraceClassLoading&lt;/h3&gt; &lt;p&gt;监控类的加载。格式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;•[Loaded java.lang.Object from shared objects file] •[Loaded java.io.Serializable from shared objects file] •[Loaded java.lang.Comparable from shared objects file] •[Loaded java.lang.CharSequence from shared objects file] •[Loaded java.lang.String from shared objects file] •[Loaded java.lang.reflect.GenericDeclaration from shared objects file] •[Loaded java.lang.reflect.Type from shared objects file] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用这个参数就能很清楚的看到那些类被加载的情况了。&lt;/p&gt; &lt;h3&gt;8.-XX:+PrintClassHistogram&lt;/h3&gt; &lt;p&gt;跟踪参数。这个按下Ctrl+Break后，就会打印一下信息：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;num     #instances         #bytes  class name    ----------------------------------------------       1:        890617      470266000  [B       2:        890643       21375432  java.util.HashMap$Node       3:        890608       14249728  java.lang.Long       4:            13        8389712  [Ljava.util.HashMap$Node;       5:          2062371680  [C       6:           46341904  java.lang.Class &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;–分别显示：序号、实例数量、总大小、类型。&lt;/p&gt; &lt;p&gt;这里面那个类型，B和C的其实就是byte和char类型。&lt;/p&gt; &lt;h3&gt;9.-Xmx -Xms&lt;/h3&gt; &lt;p&gt;这个就表示设置堆内存的最大值和最小值。这个设置了最大值和最小值后，jvm启动后，并不会直接让堆内存就扩大到指定的最大数值。而是会先开辟指定的最小堆内存，如果经过数次GC后，还不能，满足程序的运行，才会逐渐的扩容堆的大小，但也不是直接扩大到最大内存。&lt;/p&gt; &lt;h3&gt;10.-Xmn&lt;/h3&gt; &lt;p&gt;设置新生代的内存大小。&lt;/p&gt; &lt;h3&gt;11.-XX:NewRatio&lt;/h3&gt; &lt;p&gt;新生代和老年代的比例。比如：1：4，就是新生代占五分之一。&lt;/p&gt; &lt;h3&gt;12.-XX:SurvivorRatio&lt;/h3&gt; &lt;p&gt;设置两个Survivor区和eden区的比例。比如：2：8 ，就是一个Survivor区占十分之一。&lt;/p&gt; &lt;h3&gt;13.-XX:+HeapDumpOnOutMemoryError&lt;/h3&gt; &lt;p&gt;发生OOM时，导出堆的信息到文件。&lt;/p&gt; &lt;h3&gt;14.-XX:+HeapDumpPath&lt;/h3&gt; &lt;p&gt;表示，导出堆信息的文件路径。&lt;/p&gt; &lt;h3&gt;15.-XX:OnOutOfMemoryError&lt;/h3&gt; &lt;p&gt;当系统产生OOM时，执行一个指定的脚本，这个脚本可以是任意功能的。比如生成当前线程的dump文件，或者是发送邮件和重启系统。&lt;/p&gt; &lt;h3&gt;16.-XX:PermSize -XX:MaxPermSize&lt;/h3&gt; &lt;p&gt;设置永久区的内存大小和最大值。永久区内存用光也会导致OOM的发生。&lt;/p&gt; &lt;h3&gt;17.-Xss&lt;/h3&gt; &lt;p&gt;设置栈的大小。栈都是每个线程独有一个，所有一般都是几百k的大小。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 02 Apr 2022 08:14:00 GMT</pubDate>
    </item>
    <item>
      <title>公司用的 MySQL 团队开发规范，太详细了，建议收藏！</title>
      <link>https://maruifu.cn/article/231</link>
      <content:encoded>&lt;h2&gt;数据库对象命名规范&lt;/h2&gt; &lt;h3&gt;数据库对象&lt;/h3&gt; &lt;p&gt;数据库对象是数据库的组成部分，常见的有以下几种：表（Table ）、索引（Index）、视图（View）、图表（Diagram）、缺省值（Default）、规则（Rule）、触发器（Trigger）、存储过程（Stored Procedure）、 用户（User）等。命名规范是指数据库对象如数据库（SCHEMA）、表（TABLE）、索引（INDEX）、约束（CONSTRAINTS）等的命名约定。&lt;/p&gt; &lt;h3&gt;数据库对象全局命名规范&lt;/h3&gt; &lt;p&gt;1、命名使用具有意义的英文词汇，词汇中间以下划线分隔&lt;/p&gt; &lt;p&gt;2、命名只能使用英文字母、数字、下划线，以英文字母开头&lt;/p&gt; &lt;p&gt;3、避免用MySQL的保留字如：backup、call、group等&lt;/p&gt; &lt;p&gt;4、所有数据库对象使用小写字母，实际上MySQL中是可以设置大小写是否敏感的，为了保证统一性，我们这边规范全部小写表示。&lt;/p&gt; &lt;h3&gt;数据库命名规范&lt;/h3&gt; &lt;p&gt;1、数据库命名尽量不超过30个字符。&lt;/p&gt; &lt;p&gt;2、数据库命名一般为项目名称+代表库含义的简写，比如IM项目的工作流数据库，可以是 im_flow。&lt;/p&gt; &lt;p&gt;3、数据库创建时必须添加默认字符集和校对规则子句。默认字符集为UTF8（已迁移dumbo的使用utf8mb4）&lt;/p&gt; &lt;p&gt;4、命名应使用小写。&lt;/p&gt; &lt;h3&gt;表命名规范&lt;/h3&gt; &lt;p&gt;1、常规表表名以t_开头，t代表table的意思，命名规则即 t + 模块（包含模块含义的简写）+ 表（包含表含义的简写），比如用户模块的教育信息表：t_user_eduinfo。&lt;/p&gt; &lt;p&gt;2、临时表（RD、QA或DBA同学用于数据临时处理的表），命名规则：temp前缀+模块+表+日期后缀：temp_user_eduinfo_20210719&lt;/p&gt; &lt;p&gt;3、备份表（用于保存和归档历史数据或者作为灾备恢复的数据）命名规则，bak前缀+模块+表+日期后缀：bak_user_eduinfo_20210719&lt;/p&gt; &lt;p&gt;4、同一个模块的表尽可能使用相同的前缀，表名称尽可能表达含义&lt;/p&gt; &lt;p&gt;5、多个单词以下划线 _ 分隔&lt;/p&gt; &lt;p&gt;6、常规表表名尽量不超过30个字符，temp表和bak表视情况而定，也尽量简短为宜，命名应使用小写&lt;/p&gt; &lt;h3&gt;字段命名规范&lt;/h3&gt; &lt;p&gt;1、字段命名需要表示其实际含义的英文单词或简写，单词之间用下划线 _ 进行连接，如 service_ip、service_port。&lt;/p&gt; &lt;p&gt;2、各表之间相同意义的字段必须同名，比如a表和b表都有创建时间，应该统一为create_time，不一致会很混乱。&lt;/p&gt; &lt;p&gt;3、多个单词以下划线 _ 分隔&lt;/p&gt; &lt;p&gt;4、字段名尽量不超过30个字符，命名应该使用小写&lt;/p&gt; &lt;h3&gt;索引命名规范&lt;/h3&gt; &lt;p&gt;1、唯一索引使用uni + 字段名 来命名：create unique index uni_uid on t_user_basic(uid) 。&lt;/p&gt; &lt;p&gt;2、非唯一索引使用idx + 字段名 来命名：create index idx_uname_mobile on t_user_basic(uname,mobile) 。&lt;/p&gt; &lt;p&gt;3、多个单词以下划线 _ 分隔。&lt;/p&gt; &lt;p&gt;4、索引名尽量不超过50个字符，命名应该使用小写，组合索引的字段不宜太多，不然也不利于查询效率的提升。&lt;/p&gt; &lt;p&gt;5、多单词组成的列名，取尽可能代表意义的缩写，如 test_contact表member_id和friend_id上的组合索引：idx_mid_fid。&lt;/p&gt; &lt;p&gt;6、理解组合索引最左前缀原则，避免重复建设索引，如果建立了(a,b,c)，相当于建立了(a), (a,b), (a,b,c)。&lt;/p&gt; &lt;h3&gt;视图命名规范&lt;/h3&gt; &lt;p&gt;1、视图名以v开头，表示view，完整结构是v+视图内容含义缩写。&lt;/p&gt; &lt;p&gt;2、如果视图只来源单个表，则为v+表名。如果视图由几个表关联产生就用v+下划线（_）连接几个表名，视图名尽量不超过30个字符。如超过30个字符则取简写。&lt;/p&gt; &lt;p&gt;3、如无特殊需要，严禁开发人员创建视图。&lt;/p&gt; &lt;p&gt;4、命名应使用小写。&lt;/p&gt; &lt;h3&gt;存储过程命名规范&lt;/h3&gt; &lt;p&gt;1、存储过程名以sp开头，表示存储过程（storage procedure）。之后多个单词以下划线（_）进行连接。存储过程命名中应体现其功能。存储过程名尽量不能超过30个字符。&lt;/p&gt; &lt;p&gt;2、存储过程中的输入参数以i_开头，输出参数以o_开头。&lt;/p&gt; &lt;p&gt;3、命名应使用小写。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  create procedure sp_multi_param(in i_id bigint,in i_name varchar(32),out o_memo varchar(100))   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;函数命名规范&lt;/h3&gt; &lt;p&gt;1、函数名以func开始，表示function。之后多个单词以下划线（_）进行连接，函数命名中应体现其功能。函数名尽量不超过30个字符。&lt;/p&gt; &lt;p&gt;2、命名应使用小写。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  create function func_format_date(ctime datetime) &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;触发器命名规范&lt;/h3&gt; &lt;p&gt;1、触发器以trig开头，表示trigger 触发器。&lt;/p&gt; &lt;p&gt;2、基本部分，描述触发器所加的表，触发器名尽量不超过30个字符。&lt;/p&gt; &lt;p&gt;3、后缀（_i,_u,_d）,表示触发条件的触发方式（insert,update或delete）。&lt;/p&gt; &lt;p&gt;4、命名应使用小写。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  DROP TRIGGER IF EXISTS trig_attach_log_d;   CREATE TRIGGER trig_attach_log_d AFTER DELETE ON t_dept FOR EACH ROW;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;约束命名规范&lt;/h3&gt; &lt;p&gt;1、唯一约束：uk_表名称_字段名。uk是UNIQUE KEY的缩写。比如给一个部门的部门名称加上唯一约束，来保证不重名，如下：ALTER TABLE t_dept ADD CONSTRAINT un_name UNIQUE(name);&lt;/p&gt; &lt;p&gt;2、外键约束：fk_表名，后面紧跟该外键所在的表名和对应的主表名（不含t_）。子表名和父表名用下划线(_)分隔。如下：ALTER TABLE t_user ADD CONSTRAINT fk_user_dept FOREIGN KEY(depno) REFERENCES t_dept (id);&lt;/p&gt; &lt;p&gt;3、非空约束：如无特殊需要，建议所有字段默认非空(not null)，不同数据类型必须给出默认值(default)。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  `id` int(11) NOT NULL,   `name` varchar(30) DEFAULT '',   `deptId` int(11) DEFAULT 0,   `salary` float DEFAULT NULL,  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4、出于性能考虑，如无特殊需要，建议不使用外键。参照完整性由代码控制。这个也是我们普遍的做法，从程序角度进行完整性控制，但是如果不注意，也会产生脏数据。&lt;/p&gt; &lt;p&gt;5、命名应使用小写。&lt;/p&gt; &lt;h3&gt;用户命名规范&lt;/h3&gt; &lt;p&gt;1、 生产使用的用户命名格式为 code_应用&lt;/p&gt; &lt;p&gt;2、 只读用户命名规则为 read_应用&lt;/p&gt; &lt;h2&gt;[数据库对象设计规范&lt;/h2&gt; &lt;h3&gt;存储引擎的选择&lt;/h3&gt; &lt;p&gt;1、如无特殊需求，必须使用innodb存储引擎。&lt;/p&gt; &lt;p&gt;可以通过 show variables like 'default_storage_engine' 来查看当前默认引擎。主要有MyISAM 和 InnoDB，从5.5版本开始默认使用 InnoDB 引擎。&lt;/p&gt; &lt;p&gt;基本的差别为：MyISAM类型不支持事务处理等高级处理，而InnoDB类型支持。MyISAM类型的表强调的是性能，其执行速度比InnoDB类型更快，但是不提供事务支持，而InnoDB提供事务支持以及外部键等高级数据库功能。&lt;/p&gt; &lt;h3&gt;字符集的选择&lt;/h3&gt; &lt;p&gt;1、如无特殊要求，必须使用utf8或utf8mb4。&lt;/p&gt; &lt;p&gt;在国内，选择对中文和各语言支持都非常完善的&lt;code&gt;utf8&lt;/code&gt;格式是最好的方式，MySQL在5.5之后增加utf8mb4编码，mb4就是most bytes 4的意思，专门用来兼容四字节的unicode。&lt;/p&gt; &lt;p&gt;所以utf8mb4是utf8的超集，除了将编码改为utf8mb4外不需要做其他转换。当然，为了节省空间，一般情况下使用utf8也就够了。&lt;/p&gt; &lt;p&gt;可以使用如下脚本来查看数据库的编码格式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1 SHOW VARIABLES WHERE Variable_name LIKE 'character_set_%' OR Variable_name LIKE 'collation%'; 2 -- 或 3 SHOW VARIABLES Like '%char%';   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;表设计规范&lt;/h3&gt; &lt;p&gt;1、不同应用间所对应的数据库表之间的关联应尽可能减少，不允许使用外键对表之间进行关联，确保组件对应的表之间的独立性，为系统或表结构的重构提供可能性。目前业内的做法一般 由程序控制参照完整性。&lt;/p&gt; &lt;p&gt;2、表设计的角度不应该针对整个系统进行数据库设计，而应该根据系统架构中组件划分，针对每个组件所处理的业务进行数据库设计。&lt;/p&gt; &lt;p&gt;3、表必须要有PK，主键的优势是唯一标识、有效引用、高效检索，所以一般情况下尽量有主键字段。&lt;/p&gt; &lt;p&gt;4、一个字段只表示一个含义。&lt;/p&gt; &lt;p&gt;5、表不应该有重复列。&lt;/p&gt; &lt;p&gt;6、禁止使用复杂数据类型(数组,自定义等)，Json类型的使用视情况而定。&lt;/p&gt; &lt;p&gt;7、需要join的字段(连接键)，数据类型必须保持绝对一致，避免隐式转换。比如关联的字段都是int类型。&lt;/p&gt; &lt;p&gt;8、设计应至少满足第三范式,尽量减少数据冗余。一些特殊场景允许反范式化设计，但在项目评审时需要对冗余字段的设计给出解释。&lt;/p&gt; &lt;p&gt;9、TEXT字段作为大体量文本存储，必须放在独立的表中 , 用PK与主表关联。如无特殊需要，禁止使用TEXT、BLOB字段。&lt;/p&gt; &lt;p&gt;10、需要定期删除(或者转移)过期数据的表，通过分表解决，我们的做法是按照2/8法则将操作频率较低的历史数据迁移到历史表中，按照时间或者则曾Id做切割点。&lt;/p&gt; &lt;p&gt;11、单表字段数不要太多，建议最多不要大于50个。过度的宽表对性能也是很大的影响。&lt;/p&gt; &lt;p&gt;12、MySQL在处理大表时，性能就开始明显降低，所以建议单表物理大小限制在16GB，表中数据行数控制在2000W内。&lt;/p&gt; &lt;p&gt;业内的规则是超过2000W性能开始明显降低。但是这个值是灵活的，你可以根据实际情况进行测试来判断，比如阿里的标准就是500W，百度的确是2000W。实际上是否宽表，单行数据所占用的空间都有起到作用的。&lt;/p&gt; &lt;p&gt;13、如果数据量或数据增长在前期规划时就较大，那么在设计评审时就应加入分表策略，后续会有专门的文章来分析数据拆分的做法：垂直拆分（垂直分库和垂直分表）、水平拆分（分库分表和库内分表）；&lt;/p&gt; &lt;p&gt;14、无特殊需求，严禁使用分区表&lt;/p&gt; &lt;h3&gt;字段设计规范&lt;/h3&gt; &lt;p&gt;1、INT：如无特殊需要，存放整型数字使用UNSIGNED INT型，整型字段后的数字代表显示长度。比如 &lt;code&gt;id&lt;/code&gt; int(11) NOT NULL&lt;/p&gt; &lt;p&gt;2、DATETIME：所有需要精确到时间(时分秒)的字段均使用DATETIME,不要使用TIMESTAMP类型。&lt;/p&gt; &lt;p&gt;对于TIMESTAMP，它把写入的时间从当前时区转化为UTC（世界标准时间）进行存储。查询时，将其又转化为客户端当前时区进行返回。而对于DATETIME，不做任何改变，基本上是原样输入和输出。&lt;/p&gt; &lt;p&gt;另外DATETIME存储的范围也比较大：&lt;/p&gt; &lt;p&gt;timestamp所能存储的时间范围为：'1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999'。&lt;/p&gt; &lt;p&gt;datetime所能存储的时间范围为：'1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999'。&lt;/p&gt; &lt;p&gt;但是特殊情况，对于跨时区的业务，TIMESTAMP更为合适。&lt;/p&gt; &lt;p&gt;3、VARCHAR：所有动态长度字符串 全部使用VARCHAR类型,类似于状态等有限类别的字段,也使用可以比较明显表示出实际意义的字符串,而不应该使用INT之类的数字来代替；VARCHAR(N)，&lt;/p&gt; &lt;p&gt;N表示的是字符数而不是字节数。比如VARCHAR(255)，可以最大可存储255个字符（字符包括英文字母，汉字，特殊字符等）。但N应尽可能小，因为MySQL一个表中所有的VARCHAR字段最大长度是65535个字节，且存储字符个数由所选字符集决定。&lt;/p&gt; &lt;p&gt;如UTF8存储一个字符最大要3个字节，那么varchar在存放占用3个字节长度的字符时不应超过21845个字符。同时，在进行排序和创建临时表一类的内存操作时，会使用N的长度申请内存。(如无特殊需要，原则上单个varchar型字段不允许超过255个字符)&lt;/p&gt; &lt;p&gt;4、TEXT：仅仅当字符数量可能超过20000个的时候,才可以使用TEXT类型来存放字符类数据,因为所有MySQL数据库都会使用UTF8字符集。&lt;/p&gt; &lt;p&gt;所有使用TEXT类型的字段必须和原表进行分拆，与原表主键单独组成另外一个表进行存放，与大文本字段的隔离，目的是。如无特殊需要，不使用MEDIUMTEXT、TEXT、LONGTEXT类型&lt;/p&gt; &lt;p&gt;5、对于精确浮点型数据存储，需要使用DECIMAL，严禁使用FLOAT和DOUBLE。&lt;/p&gt; &lt;p&gt;6、如无特殊需要，尽量不使用BLOB类型&lt;/p&gt; &lt;p&gt;7、如无特殊需要，字段建议使用NOT NULL属性，可用默认值代替NULL&lt;/p&gt; &lt;p&gt;8、自增字段类型必须是整型且必须为UNSIGNED，推荐类型为INT或BIGINT，并且自增字段必须是主键或者主键的一部分。&lt;/p&gt; &lt;h3&gt;索引设计规范&lt;/h3&gt; &lt;p&gt;1、索引区分度&lt;/p&gt; &lt;p&gt;索引必须创建在索引选择性（区分度）较高的列上，选择性的计算方式为:  selecttivity = count(distinct c_name)/count(*) ; 如果区分度结果小于0.2，则不建议在此列上创建索引，否则大概率会拖慢SQL执行&lt;/p&gt; &lt;p&gt;2、遵循最左前缀&lt;/p&gt; &lt;p&gt;对于确定需要组成组合索引的多个字段，设计时建议将选择性高的字段靠前放。使用时，组合索引的首字段，必须在where条件中，且需要按照最左前缀规则去匹配。&lt;/p&gt; &lt;p&gt;3、禁止使用外键，可以在程序级别来约束完整性&lt;/p&gt; &lt;p&gt;4、Text类型字段如果需要创建索引，必须使用前缀索引&lt;/p&gt; &lt;p&gt;5、单张表的索引数量理论上应控制在5个以内。经常有大批量插入、更新操作表，应尽量少建索引，索引建立的原则理论上是多读少写的场景。&lt;/p&gt; &lt;p&gt;6、ORDER BY，GROUP BY，DISTINCT的字段需要添加在索引的后面，形成覆盖索引&lt;/p&gt; &lt;p&gt;7、正确理解和计算索引字段的区分度，文中有计算规则，区分度高的索引，可以快速得定位数据，区分度太低，无法有效的利用索引，可能需要扫描大量数据页，和不使用索引没什么差别。&lt;/p&gt; &lt;p&gt;8、正确理解和计算前缀索引的字段长度，文中有判断规则，合适的长度要保证高的区分度和最恰当的索引存储容量，只有达到最佳状态，才是保证高效率的索引。&lt;/p&gt; &lt;p&gt;9、联合索引注意最左匹配原则：必须按照从左到右的顺序匹配，MySQL会一直向右匹配索引直到遇到范围查询(&amp;gt;、&amp;lt;、between、like)然后停止匹配。&lt;/p&gt; &lt;p&gt;如：depno=1 and empname&amp;gt;'' and job=1 如果建立(depno,empname,job)顺序的索引，job是用不到索引的。&lt;/p&gt; &lt;p&gt;10、应需而取策略，查询记录的时候，不要一上来就使用*，只取需要的数据，可能的话尽量只利用索引覆盖，可以减少回表操作，提升效率。&lt;/p&gt; &lt;p&gt;11、正确判断是否使用联合索引（上面联合索引的使用那一小节有说明判断规则），也可以进一步分析到索引下推（IPC），减少回表操作，提升效率。&lt;/p&gt; &lt;p&gt;12、避免索引失效的原则：禁止对索引字段使用函数、运算符操作，会使索引失效。这是实际上就是需要保证索引所对应字段的”干净度“。&lt;/p&gt; &lt;p&gt;13、避免非必要的类型转换，字符串字段使用数值进行比较的时候会导致索引无效。&lt;/p&gt; &lt;p&gt;14、模糊查询'%value%'会使索引无效，变为全表扫描，因为无法判断扫描的区间，但是'value%'是可以有效利用索引。&lt;/p&gt; &lt;p&gt;15、索引覆盖排序字段，这样可以减少排序步骤，提升查询效率&lt;/p&gt; &lt;p&gt;16、尽量的扩展索引，非必要不新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可。&lt;/p&gt; &lt;p&gt;举例子：比如一个品牌表，建立的的索引如下，一个主键索引，一个唯一索引&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  PRIMARY KEY (`id`),   UNIQUE KEY `uni_brand_define` (`app_id`,`define_id`) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当你同事业务代码中的检索语句如下的时候，应该立即警告了，即没有覆盖索引，也没按照最左前缀原则：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  select brand_id,brand_name from  ds_brand_system where status=?  and define_id=?  and app_id=? &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;建议改成如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  select brand_id,brand_name from  ds_brand_system where app_id=? and define_id=?  and  status=?  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;约束设计规范&lt;/h3&gt; &lt;p&gt;1、PK应该是有序并且无意义的，由开发人员自定义，尽可能简短，并且是自增序列。&lt;/p&gt; &lt;p&gt;2、表中除PK以外,还存在唯一性约束的,可以在数据库中创建以“uk_”作为前缀的唯一约束索引。&lt;/p&gt; &lt;p&gt;3、PK字段不允许更新。&lt;/p&gt; &lt;p&gt;4、禁止创建外键约束，外键约束由程序控制。&lt;/p&gt; &lt;p&gt;5、如无特殊需要，所有字段必须添加非空约束，即not null。&lt;/p&gt; &lt;p&gt;6、如无特殊需要，所有字段必须有默认值。&lt;/p&gt; &lt;h2&gt;SQL使用规范&lt;/h2&gt; &lt;h3&gt;select 检索的规范性&lt;/h3&gt; &lt;p&gt;1、尽量避免使用&lt;code&gt;select *&lt;/code&gt;，join语句使用&lt;code&gt;select *&lt;/code&gt;可能导致只需要访问索引即可完成的查询需要回表取数。&lt;/p&gt; &lt;p&gt;一种是可能取出很多不需要的数据，对于宽表来说，这是灾难；一种是尽可能避免回表，因为取一些根本不需要的数据而回表导致性能低下，是很不合算。&lt;/p&gt; &lt;p&gt;2、严禁使用 select * from t_name ，而不加任何where条件，道理一样，这样会变成全表全字段扫描。&lt;/p&gt; &lt;p&gt;3、MySQL中的text类型字段存储：&lt;/p&gt; &lt;p&gt;3.1、不与其他普通字段存放在一起,因为读取效率低，也会影响其他轻量字段存取效率。&lt;/p&gt; &lt;p&gt;3.2、如果不需要text类型字段，又使用了select *，会让该执行消耗大量io，效率也很低下&lt;/p&gt; &lt;p&gt;4、在取出字段上可以使用相关函数，但应尽可能避免出现 now() , rand() , sysdate() 等不确定结果的函数，在Where条件中的过滤条件字段上严禁使用任何函数，包括数据类型转换函数。大量的计算和转换会造成效率低下，这个在索引那边也描述过了。&lt;/p&gt; &lt;p&gt;5、分页查询语句全部都需要带有排序条件 , 否则很容易引起乱序&lt;/p&gt; &lt;p&gt;6、用in()/union替换or，效率会好一些，并注意in的个数小于300&lt;/p&gt; &lt;p&gt;7、严禁使用%前缀进行模糊前缀查询:如：select a,b,c from t_name where a like ‘%name’; 可以使用%模糊后缀查询如：select a,b from t_name where a like ‘name%’;&lt;/p&gt; &lt;p&gt;8、避免使用子查询，可以把子查询优化为join操作&lt;/p&gt; &lt;p&gt;通常子查询在in子句中，且子查询中为简单SQL(不包含union、group by、order by、limit从句)时，才可以把子查询转化为关联查询进行优化。&lt;/p&gt; &lt;p&gt;子查询性能差的原因：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;·&lt;/strong&gt; 子查询的结果集无法使用索引，通常子查询的结果集会被存储到临时表中，不论是内存临时表还是磁盘临时表都不会存在索引，所以查询性能 会受到一定的影响；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;·&lt;/strong&gt; 特别是对于返回结果集比较大的子查询，其对查询性能的影响也就越大；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;·&lt;/strong&gt; 由于子查询会产生大量的临时表也没有索引，所以会消耗过多的CPU和IO资源，产生大量的慢查询。&lt;/p&gt; &lt;h3&gt;操作的规范性&lt;/h3&gt; &lt;p&gt;1、禁止使用不含字段列表的INSERT语句&lt;/p&gt; &lt;p&gt;如：insert into values ('a','b','c');  应使用  insert into t_name(c1,c2,c3) values ('a','b','c'); 。&lt;/p&gt; &lt;p&gt;2、大批量写操作（UPDATE、DELETE、INSERT），需要分批多次进行操作&lt;/p&gt; &lt;p&gt;&lt;strong&gt;·&lt;/strong&gt; 大批量操作可能会造成严重的主从延迟，特别是主从模式下，大批量操作可能会造成严重的主从延迟，因为需要slave从master的binlog中读取日志来进行数据同步。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;·&lt;/strong&gt; binlog日志为row格式时会产生大量的日志&lt;/p&gt; &lt;h2&gt;程序上的约束&lt;/h2&gt; &lt;p&gt;后续我们团队的目标是研发评审工具对开发同学提交的建库、建表、刷数据、查询的语句进行分析，看看是否符合应有的规范。如果不符合，驳回修改。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 02 Apr 2022 08:11:00 GMT</pubDate>
    </item>
    <item>
      <title>如何防止 Java 代码被反编译</title>
      <link>https://maruifu.cn/article/230</link>
      <content:encoded>&lt;p&gt;由于Java字节码的抽象级别较高，因此它们较容易被反编译。本节介绍了几种常用的方法，用于保护Java字节码不被反编译。通常，这些方法不能够绝对防止程序被反编译，而是加大反编译的难度而已，因为这些方法都有自己的使用环境和弱点。&lt;/p&gt; &lt;h2&gt;隔离Java程序&lt;/h2&gt; &lt;p&gt;最简单的方法就是让用户不能够访问到Java Class程序，这种方法是最根本的方法，具体实现有多种方式。例如，开发人员可以将关键的Java Class放在服务器端，客户端通过访问服务器的相关接口来获得服务，而不是直接访问Class文件。&lt;/p&gt; &lt;p&gt;这样黑客就没有办法[反编译Class文件。目前，通过接口提供服务的标准和协议也越来越多，例如 HTTP、Web Service、RPC等。但是有很多应用都不适合这种保护方式，例如对于单机运行的程序就无法隔离Java程序。这种保护方式见图1所示。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/640.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h2&gt;对Class文件进行加密&lt;/h2&gt; &lt;p&gt;为了防止Class文件被直接反编译，许多开发人员将一些关键的Class文件进行加密，例如对注册码、序列号管理相关的类等。在使用这些被加密的类之前，程序首先需要对这些类进行解密，而后再将这些类装载到JVM当中。这些类的解密可以由硬件完成，也可以使用软件完成。&lt;/p&gt; &lt;p&gt;在实现时，开发人员往往通过自定义ClassLoader类来完成加密类的装载(注意由于安全性的原因，Applet不能够支持自定义的 ClassLoader)。自定义的ClassLoader首先找到加密的类，而后进行解密，最后将解密后的类装载到JVM当中。&lt;/p&gt; &lt;p&gt;在这种保护方式中，自定义的ClassLoader是非常关键的类。由于它本身不是被加密的，因此它可能成为黑客最先攻击的目标。如果相关的解密密钥和算法被攻克，那么被加密的类也很容易被解密。这种保护方式示意图见图2。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/64029b1bd95507dc2b2.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h2&gt;转换成本地代码&lt;/h2&gt; &lt;p&gt;将程序转换成本地代码也是一种防止反编译的有效方法。因为本地代码往往难以被反编译。开发人员可以选择将整个应用程序转换成本地代码，也可以选择关键模块转换。如果仅仅转换关键部分模块，Java程序在使用这些模块时，需要使用JNI技术进行调用。&lt;/p&gt; &lt;p&gt;当然，在使用这种技术保护Java程序的同时，也牺牲了Java的跨平台特性。对于不同的平台，我们需要维护不同版本的本地代码，这将加重软件支持和维护的工作。不过对于一些关键的模块，有时这种方案往往是必要的。&lt;/p&gt; &lt;p&gt;为了保证这些本地代码不被修改和替代，通常需要对这些代码进行数字签名。在使用这些本地代码之前，往往需要对这些本地代码进行认证，确保这些代码没有被黑客更改。如果签名检查通过，则调用相关JNI方法。这种保护方式示意图见图3。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/6408d6405e4a8ab43cb.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;h2&gt;代码混淆&lt;/h2&gt; &lt;p&gt;代码混淆是对Class文件进行重新组织和处理，使得处理后的代码与处理前代码完成相同的功能(语义)。但是混淆后的代码很难被[反编译，即[反编译后得出的代码是非常难懂、晦涩的，因此[反编译人员很难得出程序的真正语义。&lt;/p&gt; &lt;p&gt;从理论上来说，黑客如果有足够的时间，被混淆的代码仍然可能被破解，甚至目前有些人正在研制反混淆的工具。但是从实际情况来看，由于混淆技术的多元化发展，混淆理论的成熟，经过混淆的Java代码还是能够很好地防止[反编译。下面我们会详细介绍混淆技术，因为混淆是一种保护Java程序的重要技术。图4是代码混淆的示图。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/640da9de5e363928598.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;图4 代码混淆示意图&lt;/p&gt; &lt;p&gt;几种技术的总结 以上几种技术都有不同的应用环境，各自都有自己的弱点，表1是相关特点的比较。&lt;/p&gt; &lt;p&gt;混淆技术介绍&lt;/p&gt; &lt;p&gt;表1 不同保护技术比较表&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/6405d1b9b090bea1d85.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;到目前为止，对于Java程序的保护，混淆技术还是最基本的保护方法。Java混淆工具也非常多，包括商业的、免费的、开放源代码的。Sun公司也提供了自己的混淆工具。它们大多都是对Class文件进行混淆处理，也有少量工具首先对源代码进行处理，然后再对Class进行处理，这样加大了混淆处理的力度。&lt;/p&gt; &lt;p&gt;目前，商业上比较成功的混淆工具包括JProof公司的1stBarrier系列、Eastridge公司的JShrink和 4thpass.com的SourceGuard等。主要的混淆技术按照混淆目标可以进行如下分类，它们分别为符号混淆(Lexical Obfuscation)、数据混淆(Data Obfuscation)、控制混淆(Control Obfuscation)、预防性混淆(Prevent Transformation)。&lt;/p&gt; &lt;h2&gt;符号混淆&lt;/h2&gt; &lt;p&gt;在Class中存在许多与程序执行本身无关的信息，例如方法名称、变量名称，这些符号的名称往往带有一定的含义。例如某个方法名为 getKeyLength()，那么这个方法很可能就是用来返回Key的长度。&lt;/p&gt; &lt;p&gt;符号混淆就是将这些信息打乱，把这些信息变成无任何意义的表示，例如将所有的变量从vairant_001开始编号；对于所有的方法从method_001开始编号。&lt;/p&gt; &lt;p&gt;这将对反编译带来一定的困难。对于私有函数、局部变量，通常可以改变它们的符号，而不影响程序的运行。但是对于一些接口名称、公有函数、成员变量，如果有其它外部模块需要引用这些符号，我们往往需要保留这些名称，否则外部模块找不到这些名称的方法和变量。因此，多数的混淆工具对于符号混淆，都提供了丰富的选项，让用户选择是否、如何进行符号混淆。&lt;/p&gt; &lt;h2&gt;数据混淆&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/640384d02f70c5d1026.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;数据混淆是对程序使用的数据进行混淆。混淆的方法也有多种，主要可以分为改变数据存储及编码(Store and Encode Transform)、改变数据访问(Access Transform)。&lt;/p&gt; &lt;p&gt;改变数据存储和编码可以打乱程序使用的数据存储方式。例如将一个有10个成员的数组，拆开为10个变量，并且打乱这些变量的名字；将一个两维数组转化为一个一维数组等。对于一些复杂的数据结构，我们将打乱它的数据结构，例如用多个类代替一个复杂的类等。&lt;/p&gt; &lt;p&gt;另外一种方式是改变数据访问。例如访问数组的下标时，我们可以进行一定的计算，图5就是一个例子。&lt;/p&gt; &lt;p&gt;在实践混淆处理中，这两种方法通常是综合使用的，在打乱数据存储的同时，也打乱数据访问的方式。经过对数据混淆，程序的语义变得复杂了，这样增大了反编译的难度。&lt;/p&gt; &lt;h2&gt;控制混淆&lt;/h2&gt; &lt;p&gt;控制混淆就是对程序的控制流进行混淆，使得程序的控制流更加难以[反编译，通常控制流的改变需要增加一些额外的计算和控制流，因此在性能上会给程序带来一定的负面影响。有时，需要在程序的性能和混淆程度之间进行权衡。控制混淆的技术最为复杂，技巧也最多。这些技术可以分为如下几类：&lt;/p&gt; &lt;p&gt;增加混淆控制通过增加额外的、复杂的控制流，可以将程序原来的语义隐藏起来。例如，对于按次序执行的两个语句A、B，我们可以增加一个控制条件，以决定B的执行。通过这种方式加大反汇编的难度。但是所有的干扰控制都不应该影响B的执行。图6就给出三种方式，为这个例子增加混淆控制。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/640787ee719bdd381a8.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;图6 增加混淆控制的三种方式&lt;/p&gt; &lt;p&gt;控制流重组重组控制流也是重要的混淆方法。例如，程序调用一个方法，在混淆后，可以将该方法代码嵌入到调用程序当中。反过来，程序中的一段代码也可以转变为一个函数调用。另外，对于一个循环的控制流，为可以拆分多个循环的控制流，或者将循环转化成一个递归过程。这种方法最为复杂，研究的人员也非常多。&lt;/p&gt; &lt;h2&gt;预防性混淆&lt;/h2&gt; &lt;p&gt;这种混淆通常是针对一些专用的反编译器而设计的，一般来说，这些技术利用反编译器的弱点或者Bug来设计混淆方案。例如，有些反编译器对于 Return后面的指令不进行反编译，而有些混淆方案恰恰将代码放在Return语句后面。这种混淆的有效性对于不同反编译器的作用也不太相同的。一个好的混淆工具，通常会综合使用这些混淆技术。&lt;/p&gt; &lt;h2&gt;案例分析&lt;/h2&gt; &lt;p&gt;在实践当中，保护一个大型Java程序经常需要综合使用这些方法，而不是单一使用某一种方法。这是因为每种方法都有其弱点和应用环境。综合使用这些方法使得Java程序的保护更加有效。另外，我们经常还需要使用其它的相关安全技术，例如安全认证、数字签名、PKI等。&lt;/p&gt; &lt;p&gt;本文给出的例子是一个Java应用程序，它是一个SCJP(Sun Certificate Java Programmer)的模拟考试软件。该应用程序带有大量的模拟题目，所有的题目都被加密后存储在文件中。由于它所带的题库是该软件的核心部分，所以关于题库的存取和访问就成为非常核心的类。一旦这些相关的类被反编译，则所有的题库将被破解。现在，我们来考虑如何保护这些题库及相关的类。&lt;/p&gt; &lt;p&gt;在这个例子中，我们考虑使用综合保护技术，其中包括本地代码和混淆技术。因为该软件主要发布在Windows上，因此转换成本地代码后，仅仅需要维护一个版本的本地代码。另外，混淆对Java程序也是非常有效的，适用于这种独立发布的应用系统。&lt;/p&gt; &lt;p&gt;在具体的方案中，我们将程序分为两个部分，一个是由本地代码编写的题库访问的模块，另外一个是由Java开发的其它模块。这样可以更高程度地保护题目管理模块不被[反编译。对于Java开发的模块，我们仍然要使用混淆技术。该方案的示意图参见图7。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/640514bbcf32d151a00.png" alt="图片" title="图片" /&gt;&lt;/p&gt; &lt;p&gt;图7 SCJP保护技术方案图&lt;/p&gt; &lt;p&gt;对于题目管理模块，由于程序主要在Windows下使用，所以使用C++开发题库访问模块，并且提供了一定的访问接口。为了保护题库访问的接口，我们还增加了一个初始化接口，用于每次使用题库访问接口之前的初始化工作。它的接口主要分为两类：&lt;/p&gt; &lt;h2&gt;初始化接口&lt;/h2&gt; &lt;p&gt;在使用题库模块之前，我们必须先调用初始化接口。在调用该接口时，客户端需要提供一个随机数作为参数。题库管理模块和客户端通过这个随机数，按一定的算法同时生成相同的SessionKey，用于加密以后输入和输出的所有数据。通过这种方式，只有授权(有效)的客户端才能够连接正确的连接，生成正确的 SessionKey，用于访问题库信息。非法的客户很难生成正确的SessionKey，因此无法获得题库的信息。如果需要建立更高的保密级别，也可以采用双向认证技术。&lt;/p&gt; &lt;h2&gt;数据访问接口&lt;/h2&gt; &lt;p&gt;认证完成之后，客户端就可以正常的访问题库数据。但是，输入和输出的数据都是由SessionKey所加密的数据。因此，只有正确的题库管理模块才能够使用题库管理模块。图8时序图表示了题库管理模块和其它部分的交互过程。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/02/6403fd60a3fb3b1295f.png" alt="图片" title="图片" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 02 Apr 2022 08:03:37 GMT</pubDate>
    </item>
    <item>
      <title>虚拟机挂了怎么办？一张图给你解决了！</title>
      <link>https://maruifu.cn/article/229</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/IMG_0425.jpg" alt="IMG_0425" title="IMG_0425" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 01 Apr 2022 14:04:19 GMT</pubDate>
    </item>
    <item>
      <title>SpringBoot性能优化大全，贼好使！</title>
      <link>https://maruifu.cn/article/228</link>
      <content:encoded>&lt;p&gt;SpringBoot 已经成为 Java 届的 No.1 框架，每天都在蹂躏着数百万的程序员们。当服务的压力上升，对 SpringBoot 服务的优化就会被提上议程。&lt;/p&gt; &lt;p&gt;本文将详细讲解 SpringBoot 服务优化的一般思路，适合收藏之。&lt;/p&gt; &lt;h2&gt;有监控才有方向&lt;/h2&gt; &lt;p&gt;在开始对 SpringBoot 服务进行性能优化之前，我们需要做一些准备，把 SpringBoot 服务的一些数据暴露出来。&lt;/p&gt; &lt;p&gt;比如，你的服务用到了缓存，就需要把缓存命中率这些数据进行收集；用到了数据库连接池，就需要把连接池的参数给暴露出来。&lt;/p&gt; &lt;p&gt;我们这里采用的监控工具是 Prometheus，它是一个是时序数据库，能够存储我们的指标。SpringBoot 可以非常方便的接入到 Prometheus 中。&lt;/p&gt; &lt;p&gt;创建一个 SpringBoot 项目后，首先，加入 maven 依赖。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;      &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt;  &amp;lt;dependency&amp;gt;      &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt;      &amp;lt;artifactId&amp;gt;micrometer-registry-prometheus&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt;  &amp;lt;dependency&amp;gt;      &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt;      &amp;lt;artifactId&amp;gt;micrometer-core&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后，我们需要在 application.properties 配置文件中，开放相关的监控接口。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;management.endpoint.metrics.enabled=true management.endpoints.web.exposure.include=* management.endpoint.prometheus.enabled=true management.metrics.export.prometheus.enabled=true &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;启动之后，我们就可以通过访问 http://localhost:8080/actuator/prometheus 来获取监控数据。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214943934.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;想要监控业务数据也是比较简单的。你只需要注入一个MeterRegistry实例即可。&lt;/p&gt; &lt;p&gt;下面是一段示例代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Autowired MeterRegistry registry;  @GetMapping(&amp;quot;/test&amp;quot;) @ResponseBody public String test() {     registry.counter(&amp;quot;test&amp;quot;,             &amp;quot;from&amp;quot;, &amp;quot;127.0.0.1&amp;quot;,             &amp;quot;method&amp;quot;, &amp;quot;test&amp;quot;     ).increment();      return &amp;quot;ok&amp;quot;; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从监控连接中，我们可以找到刚刚添加的监控信息。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;test_total{from=&amp;quot;127.0.0.1&amp;quot;,method=&amp;quot;test&amp;quot;,} 5.0 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里简单介绍一下流行的 Prometheus 监控体系，Prometheus 使用拉的方式获取监控数据，这个暴露数据的过程可以交给功能更加齐全的 telegraf 组件。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214943952.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;如图，我们通常使用 Grafana 进行监控数据的展示，使用 AlertManager 组件进行提前预警。这一部分的搭建工作不是我们的重点，感兴趣的同学可自行研究。&lt;/p&gt; &lt;p&gt;下图便是一张典型的监控图，可以看到 Redis 的缓存命中率等情况。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214943975.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h2&gt;Java生成火焰图&lt;/h2&gt; &lt;p&gt;火焰图是用来分析程序运行瓶颈的工具。在纵向，表示的是调用栈的深度；横向表明的是消耗的时间。所以格子的宽度越大，越说明它可能是一个瓶颈。&lt;/p&gt; &lt;p&gt;火焰图也可以用来分析 Java 应用。可以从 github 上下载 async-profiler 的压缩包进行相关操作。&lt;/p&gt; &lt;p&gt;比如，我们把它解压到 /root/ 目录。然后以 javaagent 的方式来启动 Java 应用。&lt;/p&gt; &lt;p&gt;命令行如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -agentpath:/root/build/libasyncProfiler.so=start,svg,file=profile.svg -jar spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行一段时间后，停止进程，可以看到在当前目录下，生成了 profile.svg 文件，这个文件是可以用浏览器打开的，一层层向下浏览，即可找到需要优化的目标。&lt;/p&gt; &lt;h2&gt;Skywalking&lt;/h2&gt; &lt;p&gt;对于一个 web 服务来说，最缓慢的地方就在于数据库操作。所以，使用本地缓存和分布式缓存优化，能够获得最大的性能提升。&lt;/p&gt; &lt;p&gt;对于如何定位到复杂分布式环境中的问题，我这里想要分享另外一个工具：Skywalking。&lt;/p&gt; &lt;p&gt;Skywalking 是使用探针技术（JavaAgent）来实现的。通过在 Java 的启动参数中，加入 javaagent 的 Jar 包，即可将性能数据和调用链数据封装、发送到 Skywalking 的服务器。&lt;/p&gt; &lt;p&gt;下载相应的安装包（如果使用 ES 存储，需要下载专用的安装包），配置好存储之后，即可一键启动。&lt;/p&gt; &lt;p&gt;将 agent 的压缩包，解压到相应的目录。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;tar xvf skywalking-agent.tar.gz  -C /opt/ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在业务启动参数中加入 agent 的包。比如，原来的启动命令是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java  -jar /opt/test-service/spring-boot-demo.jar  --spring.profiles.active=dev &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;改造后的启动命令是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -javaagent:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=the-demo-name  -jar /opt/test-service/spring-boot-demo.ja  --spring.profiles.active=dev &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;访问一些服务的链接，打开 Skywalking 的 UI，即可看到下图的界面。我们可以从图中找到响应比较慢 QPS 又比较高的的接口，进行专项优化。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401215159704.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h2&gt;优化思路&lt;/h2&gt; &lt;p&gt;对一个普通的 Web 服务来说，我们来看一下，要访问到具体的数据，都要经历哪些主要的环节。&lt;/p&gt; &lt;p&gt;如下图，在浏览器中输入相应的域名，需要通过 DNS 解析到具体的 IP 地址上。为了保证高可用，我们的服务一般都会部署多份，然后使用 Nginx 做反向代理和负载均衡。&lt;/p&gt; &lt;p&gt;Nginx 根据资源的特性，会承担一部分动静分离的功能。其中，动态功能部分，会进入我们的 SpringBoot 服务&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401220117458.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;SpringBoot 默认使用内嵌的 tomcat 作为 Web 容器，使用典型的 MVC 模式，最终访问到我们的数据。&lt;/p&gt; &lt;h2&gt;HTTP优化&lt;/h2&gt; &lt;p&gt;下面我们举例来看一下，哪些动作能够加快网页的获取。为了描述方便，我们仅讨论 HTTP1.1 协议的。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;使用 CDN 加速文件获取&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;比较大的文件，尽量使用 CDN（Content Delivery Network）分发。甚至是一些常用的前端脚本、样式、图片等，都可以放到 CDN 上。CDN 通常能够加快这些文件的获取，网页加载也更加迅速。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;合理设置 Cache-Control 值&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;浏览器会判断 HTTP 头 Cache-Control 的内容，用来决定是否使用浏览器缓存，这在管理一些静态文件的时候，非常有用。&lt;/p&gt; &lt;p&gt;相同作用的头信息还有 Expires。Cache-Control 表示多久之后过期，Expires 则表示什么时候过期。&lt;/p&gt; &lt;p&gt;这个参数可以在 Nginx 的配置文件中进行设置：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {              # 缓存1年             add_header Cache-Control: no-cache, max-age=31536000; } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;减少单页面请求域名的数量&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;减少每个页面请求的域名数量，尽量保证在 4 个之内。这是因为，浏览器每次访问后端的资源，都需要先查询一次 DNS，然后找到 DNS 对应的 IP 地址，再进行真正的调用。&lt;/p&gt; &lt;p&gt;DNS 有多层缓存，比如浏览器会缓存一份、本地主机会缓存、ISP 服务商缓存等。从 DNS 到 IP 地址的转变，通常会花费 20-120ms 的时间。减少域名的数量，可加快资源的获取。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;开启 gzip&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;开启 gzip，可以先把内容压缩后，浏览器再进行解压。由于减少了传输的大小，会减少带宽的使用，提高传输效率。&lt;/p&gt; &lt;p&gt;在 Nginx 中可以很容易的开启。配置如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_comp_level 6; gzip_http_version 1.1; gzip_types text/plain application/javascript text/css; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;对资源进行压缩&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;对 JavaScript 和 CSS，甚至是 HTML 进行压缩。道理类似，现在流行的前后端分离模式，一般都是对这些资源进行压缩的。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;使用 keepalive&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;由于连接的创建和关闭，都需要耗费资源。用户访问我们的服务后，后续也会有更多的互动，所以保持长连接可以显著减少网络交互，提高性能。&lt;/p&gt; &lt;p&gt;nginx 默认开启了对客户端的 keep avlide 支持。你可以通过下面两个参数来调整它的行为。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;http {     keepalive_timeout  120s 120s;     keepalive_requests 10000; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;nginx 与后端 upstream 的长连接，需要手工开启，参考配置如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;location ~ /{         proxy_pass http://backend;        proxy_http_version 1.1;        proxy_set_header Connection &amp;quot;&amp;quot;; } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;&lt;strong&gt;Tomcat优化&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Tomcat 本身的优化，也是非常重要的一环。可以直接参考：《&lt;a href="https://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ==&amp;amp;mid=2650523823&amp;amp;idx=1&amp;amp;sn=aa5f41974950e759d373505e9b7c32f6&amp;amp;chksm=8780cf6bb0f7467d0e1b4d2d957e3633726c11f30e185d9ed354f2f6ad2ff607357f555097ef&amp;amp;scene=21#wechat_redirect" target="_blank"&gt;搞定tomcat重要参数调优！&lt;/a&gt;》&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;自定义Web容器&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;如果你的项目并发量比较高，想要修改最大线程数、最大连接数等配置信息，可以通过自定义 Web 容器的方式，代码如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@SpringBootApplication(proxyBeanMethods = false) public class App implements WebServerFactoryCustomizer&amp;lt;ConfigurableServletWebServerFactory&amp;gt; {  public static void main(String[] args) {   SpringApplication.run(PetClinicApplication.class, args);  }  @Override  public void customize(ConfigurableServletWebServerFactory factory) {   TomcatServletWebServerFactory f = (TomcatServletWebServerFactory) factory;         f.setProtocol(&amp;quot;org.apache.coyote.http11.Http11Nio2Protocol&amp;quot;);    f.addConnectorCustomizers(c -&amp;gt; {    Http11NioProtocol protocol = (Http11NioProtocol) c.getProtocolHandler();    protocol.setMaxConnections(200);    protocol.setMaxThreads(200);    protocol.setSelectorTimeout(3000);    protocol.setSessionTimeout(3000);    protocol.setConnectionTimeout(3000);   });  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注意上面的代码，我们设置了它的协议为 org.apache.coyote.http11.Http11Nio2Protocol，意思就是开启了 Nio2。&lt;/p&gt; &lt;p&gt;这个参数在 Tomcat8.0 之后才有，开启之后会增加一部分性能。对比如下：&lt;/p&gt; &lt;p&gt;默认：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost wrk2-master]# ./wrk -t2 -c100 -d30s -R2000 http://172.16.1.57:8080/owners?lastName= Running 30s test @ http://172.16.1.57:8080/owners?lastName=   2 threads and 100 connections   Thread calibration: mean lat.: 4588.131ms, rate sampling interval: 16277ms   Thread calibration: mean lat.: 4647.927ms, rate sampling interval: 16285ms   Thread Stats   Avg      Stdev     Max   +/- Stdev     Latency    16.49s     4.98s   27.34s    63.90%     Req/Sec   106.50      1.50   108.00    100.00%   6471 requests in 30.03s, 39.31MB read   Socket errors: connect 0, read 0, write 0, timeout 60 Requests/sec:    215.51 Transfer/sec:      1.31MB &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Nio2：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost wrk2-master]# ./wrk -t2 -c100 -d30s -R2000 http://172.16.1.57:8080/owners?lastName= Running 30s test @ http://172.16.1.57:8080/owners?lastName=   2 threads and 100 connections   Thread calibration: mean lat.: 4358.805ms, rate sampling interval: 15835ms   Thread calibration: mean lat.: 4622.087ms, rate sampling interval: 16293ms   Thread Stats   Avg      Stdev     Max   +/- Stdev     Latency    17.47s     4.98s   26.90s    57.69%     Req/Sec   125.50      2.50   128.00    100.00%   7469 requests in 30.04s, 45.38MB read   Socket errors: connect 0, read 0, write 0, timeout 4 Requests/sec:    248.64 Transfer/sec:      1.51MB &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;你甚至可以将 tomcat 替换成 undertow。undertow 也是一个 Web 容器，更加轻量级一些，占用的内容更少，启动的守护进程也更少，更改方式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;       &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;       &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;       &amp;lt;exclusions&amp;gt;         &amp;lt;exclusion&amp;gt;           &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;           &amp;lt;artifactId&amp;gt;spring-boot-starter-tomcat&amp;lt;/artifactId&amp;gt;         &amp;lt;/exclusion&amp;gt;       &amp;lt;/exclusions&amp;gt;     &amp;lt;/dependency&amp;gt;     &amp;lt;dependency&amp;gt;       &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;       &amp;lt;artifactId&amp;gt;spring-boot-starter-undertow&amp;lt;/artifactId&amp;gt;     &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;&lt;strong&gt;各个层次的优化方向&lt;/strong&gt;&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;Controller 层&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;controller 层用于接收前端的查询参数，然后构造查询结果。现在很多项目都采用前后端分离的架构，所以 controller 层的方法，一般会使用 @ResponseBody 注解，把查询的结果，解析成 JSON 数据返回（兼顾效率和可读性）。&lt;/p&gt; &lt;p&gt;由于 controller 只是充当了一个类似功能组合和路由的角色，所以这部分对性能的影响就主要体现在数据集的大小上。如果结果集合非常大，JSON 解析组件就要花费较多的时间进行解析。&lt;/p&gt; &lt;p&gt;大结果集不仅会影响解析时间，还会造成内存浪费。假如结果集在解析成 JSON 之前，占用的内存是 10MB，那么在解析过程中，有可能会使用 20M 或者更多的内存去做这个工作。&lt;/p&gt; &lt;p&gt;我见过很多案例，由于返回对象的嵌套层次太深、引用了不该引用的对象（比如非常大的 byte[] 对象），造成了内存使用的飙升。&lt;/p&gt; &lt;p&gt;所以，对于一般的服务，保持结果集的精简，是非常有必要的，这也是 DTO（data transfer object）存在的必要。&lt;/p&gt; &lt;p&gt;如果你的项目，返回的结果结构比较复杂，对结果集进行一次转换是非常有必要的。&lt;/p&gt; &lt;p&gt;另外，可以使用异步 Servlet 对 Controller 层进行优化。它的原理如下：Servlet 接收到请求之后，将请求转交给一个异步线程来执行业务处理，线程本身返回至容器，异步线程处理完业务以后，可以直接生成响应数据，或者将请求继续转发给其它 Servlet。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;Service 层&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;service 层用于处理具体的业务，大部分功能需求都是在这里完成的。service 层一般是使用单例模式（prototype），很少会保存状态，而且可以被 controller 复用。&lt;/p&gt; &lt;p&gt;service 层的代码组织，对代码的可读性、性能影响都比较大。我们常说的设计模式，大多数都是针对于 service 层来说的。&lt;/p&gt; &lt;p&gt;这里要着重提到的一点，就是分布式事务。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401215708873.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;如上图，四个操作分散在三个不同的资源中。要想达到一致性，需要三个不同的资源进行统一协调。&lt;/p&gt; &lt;p&gt;它们底层的协议，以及实现方式，都是不一样的。那就无法通过 Spring 提供的 Transaction 注解来解决，需要借助外部的组件来完成。&lt;/p&gt; &lt;p&gt;很多人都体验过，加入了一些保证一致性的代码，一压测，性能掉的惊掉下巴。分布式事务是性能杀手，因为它要使用额外的步骤去保证一致性，常用的方法有：两阶段提交方案、TCC、本地消息表、MQ 事务消息、分布式事务中间件等。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214943999.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;如上图，分布式事务要在改造成本、性能、实效等方面进行综合考虑。有一个介于分布式事务和非事务之间的名词，叫做柔性事务。柔性事务的理念是将业务逻辑和互斥操作，从资源层上移至业务层面。&lt;/p&gt; &lt;p&gt;关于传统事务和柔性事务，我们来简单比较一下。&lt;/p&gt; &lt;p&gt;***ACID：***关系数据库, 最大的特点就是事务处理，即满足 ACID。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;原子性（Atomicity）：事务中的操作要么都做，要么都不做。&lt;/li&gt; &lt;li&gt;一致性（Consistency）：系统必须始终处在强一致状态下。&lt;/li&gt; &lt;li&gt;隔离性（Isolation）：一个事务的执行不能被其他事务所干扰。&lt;/li&gt; &lt;li&gt;持续性（Durability）：一个已提交的事务对数据库中数据的改变是永久性的。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;***BASE：***BASE 方法通过牺牲一致性和孤立性来提高可用性和系统性能。&lt;/p&gt; &lt;p&gt;BASE 为 Basically Available，Soft-state，Eventually consistent 三者的缩写，其中 BASE 分别代表：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;基本可用（Basically Available）：系统能够基本运行、一直提供服务。&lt;/li&gt; &lt;li&gt;软状态（Soft-state）：系统不要求一直保持强一致状态。&lt;/li&gt; &lt;li&gt;最终一致性（Eventual consistency）：系统需要在某一时刻后达到一致性要求。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;互联网业务，推荐使用补偿事务，完成最终一致性。比如，通过一系列的定时任务，完成对数据的修复。&lt;/p&gt; &lt;h4&gt;Dao 层&lt;/h4&gt; &lt;p&gt;经过合理的数据缓存，我们都会尽量避免请求穿透到 Dao 层。除非你对 ORM 本身提供的缓存特性特别的熟悉，否则，都推荐你使用更加通用的方式去缓存数据。&lt;/p&gt; &lt;p&gt;Dao 层，主要在于对 ORM 框架的使用上。比如，在 JPA 中，如果加了一对多或者多对多的映射关系，而又没有开启懒加载，级联查询的时候就容易造成深层次的检索，造成了内存开销大、执行缓慢的后果。&lt;/p&gt; &lt;p&gt;在一些数据量比较大的业务中，多采用分库分表的方式。在这些分库分表组件中，很多简单的查询语句，都会被重新解析后分散到各个节点进行运算，最后进行结果合并。&lt;/p&gt; &lt;p&gt;举个例子，select count(*) from a 这句简单的 count 语句，就可能将请求路由到十几张表中去运算，最后在协调节点进行统计，执行效率是可想而知的。&lt;/p&gt; &lt;p&gt;目前，分库分表中间件，比较有代表性的是驱动层的 ShardingJdbc 和代理层的 MyCat，它们都有这样的问题。&lt;/p&gt; &lt;p&gt;这些组件提供给使用者的视图是一致的，但我们在编码的时候，一定要注意这些区别。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;总 结&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;下面我们来总结一下。我们简单看了一下 SpringBoot 常见的优化思路，介绍了三个新的性能分析工具。&lt;/p&gt; &lt;p&gt;一个是监控系统 Prometheus，可以看到一些具体的指标大小；一个是火焰图，可以看到具体的代码热点；一个是 Skywalking，可以分析分布式环境中的调用链。&lt;/p&gt; &lt;p&gt;在对性能有疑惑的时候，我们都会采用类似于神农氏尝百草的方式，综合各种测评工具的结果进行分析。&lt;/p&gt; &lt;p&gt;SpringBoot 自身的 Web 容器是 Tomcat，那我们就可以通过对 Tomcat 的调优来获取性能提升。当然，对于服务上层的负载均衡 Nginx，我们也提供了一系列的优化思路。&lt;/p&gt; &lt;p&gt;最后，我们看了在经典的 MVC 架构下，Controller、Service、Dao 的一些优化方向，并着重看了 Service 层的分布式事务问题。&lt;/p&gt; &lt;p&gt;SpringBoot 作为一个广泛应用的服务框架，在性能优化方面已经做了很多工作，选用了很多高速组件。&lt;/p&gt; &lt;p&gt;比如，数据库连接池默认使用 hikaricp，Redis 缓存框架默认使用 lettuce，本地缓存提供 caffeine 等。&lt;/p&gt; &lt;p&gt;对于一个普通的于数据库交互的 Web 服务来说，缓存是最主要的优化手，但细节决定成败。&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 01 Apr 2022 14:02:27 GMT</pubDate>
    </item>
    <item>
      <title>SpringBoot 三大开发工具，你都用过么？</title>
      <link>https://maruifu.cn/article/227</link>
      <content:encoded>&lt;h3&gt;一、SpringBoot Dedevtools&lt;/h3&gt; &lt;p&gt;他是一个让SpringBoot支持热部署的工具，下面是引用的方法&lt;/p&gt; &lt;p&gt;要么在创建项目的时候直接勾选下面的配置：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;要么给springBoot项目添加下面的依赖：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;     &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;spring-boot-devtools&amp;lt;/artifactId&amp;gt;     &amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;idea修改完代码后再按下 ctrl + f9 使其重新编译一下，即完成了热部署功能&lt;/li&gt; &lt;li&gt;eclipse是按ctrl + s保存 即可自动编译&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;如果你想一修改代码就自动重新编译，无需按ctrl+f9。只需要下面的操作：&lt;/p&gt; &lt;h5&gt;1.在idea的setting中把下面的勾都打上&lt;/h5&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214725881.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h5&gt;2.进入pom.xml,在build的反标签后给个光标，然后按Alt+Shift+ctrl+/&lt;/h5&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214629204.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h5&gt;3.然后勾选下面的东西，接着重启idea即可&lt;/h5&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214629234.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h3&gt;&lt;/h3&gt; &lt;h3&gt;&lt;strong&gt;二、Lombok&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;Lombok是简化JavaBean开发的工具，让开发者省去构造器，getter,setter的书写。另外，搜索公众号互联网架构师后台回复“面试”，获取一份惊喜礼包。&lt;/p&gt; &lt;p&gt;在项目初始化时勾选下面的配置，即可使用Lombok&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214629265.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;或者在项目中导入下面的依赖：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;     &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;     &amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用时，idea还需要下载下面的插件：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214629276.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;下面的使用的例子&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;  @AllArgsConstructor//全参构造器 @NoArgsConstructor//无参构造器 @Data//getter + setter public class User {     private Long id;     private String name;     private Integer age;     private String email; } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;三、Spring Configuration Processor&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;该工具是给实体类的属性注入开启提示，自我感觉该工具意义不是特别大！&lt;/p&gt; &lt;p&gt;因为SpringBoot存在属性注入，比如下面的实体类：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package org.lzl.HelloWorld.entity;  import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;  /**  * @author Lenovo  *  */ @Component @ConfigurationProperties(prefix = &amp;quot;mypet&amp;quot;) public class Pet {     private String nickName;     private String strain;  public String getNickName() {   return nickName;  }  public void setNickName(String nickName) {   this.nickName = nickName;  }  public String getStrain() {   return strain;  }  public void setStrain(String strain) {   this.strain = strain;  }  @Override  public String toString() {   return &amp;quot;Pet [nickName=&amp;quot; + nickName + &amp;quot;, strain=&amp;quot; + strain + &amp;quot;]&amp;quot;;  }        } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;想要在&lt;code&gt;application.properties&lt;/code&gt;和&lt;code&gt;application.yml&lt;/code&gt;中给mypet注入属性，却没有任何的提示，为了解决这一问题，我们在创建SpringBoot的时候勾选下面的场景：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/640-20220401214629294.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;或者直接在项目中添加下面的依赖:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;      &amp;lt;artifactId&amp;gt;spring-boot-configuration-processor&amp;lt;/artifactId&amp;gt;      &amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;并在build的标签中排除对该工具的打包：（减少打成jar包的大小）&lt;/p&gt; &lt;pre&gt;&lt;code&gt; &amp;lt;build&amp;gt;         &amp;lt;plugins&amp;gt;             &amp;lt;plugin&amp;gt;                 &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;                 &amp;lt;configuration&amp;gt;                     &amp;lt;excludes&amp;gt;                         &amp;lt;exclude&amp;gt;                             &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;                             &amp;lt;artifactId&amp;gt;spring-boot-configuration-processor&amp;lt;/artifactId&amp;gt;                         &amp;lt;/exclude&amp;gt;                     &amp;lt;/excludes&amp;gt;                 &amp;lt;/configuration&amp;gt;             &amp;lt;/plugin&amp;gt;         &amp;lt;/plugins&amp;gt;     &amp;lt;/build&amp;gt; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 01 Apr 2022 13:48:00 GMT</pubDate>
    </item>
    <item>
      <title>使用 Spring Boot 开发邮件系统</title>
      <link>https://maruifu.cn/article/226</link>
      <content:encoded>&lt;p&gt;电子邮件是因特网上使用得非常多的一种应用，它可以非常方便的使相隔很远的人进行通信，它主要的特点就是操作简单，快捷。现在的电子邮件系统以是存储与转发的模型为基础。邮件服务器接受、转发、提交及存储邮件。寄信人、收信人及他们的计算机都不用同时在线。寄信人和收信人只需在寄信或收信时简短的连线到邮件服务器即可。&lt;/p&gt; &lt;p&gt;互联网发展到现在，邮件服务已经成为互联网企业中必备功能之一，应用场景非常广泛，比较常见的有：用户注册、忘记密码、监控提醒、企业营销等。大多数互联网企业都会将邮件发送抽取为一个独立的微服务，对外提供接口来支持各种类型的邮件发送。&lt;/p&gt; &lt;p&gt;本篇内容会从以下几部分来给大家介绍如何开发一个邮件系统：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;电子邮件的历史&lt;/li&gt; &lt;li&gt;发送邮件涉及到哪些协议&lt;/li&gt; &lt;li&gt;介绍一个完整的邮件发送流程&lt;/li&gt; &lt;li&gt;快速体验邮件发送流程&lt;/li&gt; &lt;li&gt;介绍如何开发文本、HTML、附件、图片的邮件&lt;/li&gt; &lt;li&gt;做一个邮件系统需要考虑的因素&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/c0fc2ce0-bb2b-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;h3&gt;邮件历史&lt;/h3&gt; &lt;p&gt;我们先来回顾一下整个邮件的发展历史。&lt;/p&gt; &lt;h4&gt;电子邮件的发展&lt;/h4&gt; &lt;p&gt;电子邮件发明在 70 年代，却在 80 年才开始有人使用。70 年代的沉寂主要是由于当时使用 Arpanet 网络的人太少，网络的速度也仅为目前 56Kbps 标准速度的二十分之一，受网络速度的限制，那时的用户只能发送些简短的信息，根本别想象现在那样发送大量照片。&lt;/p&gt; &lt;p&gt;到 80 年代中期，个人电脑兴起，电子邮件开始在电脑迷以及大学生中广泛传播开来；到 90 年代中期，互联网浏览器诞生，全球网民人数激增，电子邮件被广为使用。2000 零几年的时候，那时候没有网盘，上大学的时候常常使用邮箱存储东西，那时候的邮箱也主要以网易为主；到了现在，几乎每个人都有好几个邮箱，QQ 邮箱、126 邮箱、公司邮箱等等，电子邮件已经成为人们生活和工作不可或缺的一部分。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;电子邮件发展历程：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;1974 年，因为 ARPANET 的推广，电子邮件的用户已经达到了数百人，不过他们大都是军方用户。自那之后，电子邮件开始了飞速的发展。Lawrence Roberts，这位当时为 ARPANET 服务的科学家为他的上司发明了邮件中的文件夹，以便其能够更好地梳理自己的邮件。&lt;/li&gt; &lt;li&gt;1975 年，南加州大学的 John Vittal 第一次发明了邮件相关的服务软件。&lt;/li&gt; &lt;li&gt;1977 年，现代的电子邮件系统开始出现。使用同一款软件并且联网了的计算机都可以使用 Tomlinson 的方法去发邮件。&lt;/li&gt; &lt;li&gt;1982 年，有关电子邮件第一个重要的标准出台了，这就是 SMTP（简单邮件传输协议 Simple Mail Transfer Protocol），它是第一个基于互联网基础传输电子邮件的标准。时至今日它还在被人使用。而也是在这一年，「email」这个词第一次出现了。&lt;/li&gt; &lt;li&gt;1983 年 1 月 1 日，ARPANET 正式使用 TCP/IP 取代旧的网络控制协议（NCP，Network Control Protocol），从而成为今天的互联网的基石。&lt;/li&gt; &lt;li&gt;从 80 年代中期开始，电子邮件被广泛使用。我国发出的第一封电子邮件就在 1987 年，是由北京计算机应用技术研究所发送到德国的。&lt;/li&gt; &lt;li&gt;1988 年，世界上第一个商用邮件系统 Eudora 出现，发明者是美国软件工程师 Steve Dorner。&lt;/li&gt; &lt;li&gt;1990 年，HTML 格式的邮件出现，除了文字之外，我们也能在邮件中看到图片了。&lt;/li&gt; &lt;li&gt;1992 年，MIME 协议（多用途互联网邮件扩展，Multipurpose Internet Mail Extensions）诞生，它扩展了电子邮件标准，使其能够支援更多种形式的内容。也是在这一年，微软在 MS-DOS 系统上，推出了 Outlook 邮件应用。&lt;/li&gt; &lt;li&gt;1996 年，世界上第一个以网页为基础的邮件应用 Hotmail 诞生，然后微软在下一年花了 4 亿美元买下了它。&lt;/li&gt; &lt;li&gt;……&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;世界的第一封电子邮件&lt;/h4&gt; &lt;p&gt;1969 年 10 月世界上的第一封电子邮件是由计算机科学家 Leonard K. 教授发给他的同事的一条简短消息。&lt;/p&gt; &lt;p&gt;据《互联网周刊》报道世界上的第一封电子邮件是由计算机科学家Leonard K.教授发给他的同事的一条简短消息(时间应该是1969年10月)，这条消息只有两个字母：&amp;quot;LO&amp;quot;。Leonard K. 教授因此被称为电子邮件之父。所以第一条网上信息就是‘LO’，意思是‘你好！’”&lt;/p&gt; &lt;p&gt;当然这个说法也有一点争议，另外一种说法是麻省理工学院博士 Ray Tomlinson 发送的第一封邮件，这里不再展开讨论。&lt;/p&gt; &lt;h4&gt;中国的第一封电子邮件&lt;/h4&gt; &lt;p&gt;1987 年 9 月 14 日中国第一封电子邮件是由“德国互联网之父”维纳·措恩与王运丰在当时的兵器工业部下属单位—计算机应用技术研究所（简称 ICA）发往德国卡尔斯鲁厄大学的，其内容为德文和英文双语，第一段大意如下：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;原文：&lt;em&gt;“ Across the Great Wall we can reach every corner in the world. ”&lt;/em&gt;&lt;/p&gt; &lt;p&gt;中文大意：&lt;em&gt;“ 越过长城，我们可以到达世界的每一个角落。 ”&lt;/em&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;这是中国通过北京与德国卡尔斯鲁厄大学之间的网络连接，发出的第一封电子邮件。&lt;strong&gt;现在看这封邮件内容，颇具深意！&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;邮件协议&lt;/h3&gt; &lt;p&gt;发送邮件的本质是将一个人的信息传输给另外一个人，那么如何传输就需要商量好标准，这些标准就是协议。最初只有两个协议：&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;SMTP 协议&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;SMTP 的全称是 “Simple Mail Transfer Protocol”，即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范，通过它来控制邮件的中转方式。它的一个重要特点是它能够在传送中接力传送邮件，即邮件可以通过不同网络上的主机接力式传送。&lt;/p&gt; &lt;p&gt;SMTP 认证，简单地说就是要求必须在提供了账户名和密码之后才可以登录 SMTP 服务器，这就使得那些垃圾邮件的散播者无可乘之机。增加 SMTP 认证的目的是为了使用户避免受到垃圾邮件的侵扰。&lt;strong&gt;SMTP主要负责底层的邮件系统如何将邮件从一台机器传至另外一台机器。&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;POP3 协议&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;POP3 是 Post Office Protocol 3 的简称，即邮局协议的第3个版本，它规定怎样将个人计算机连接到 Internet 的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准，POP3 允许用户从服务器上把邮件存储到本地主机（即自己的计算机）上，同时删除保存在邮件服务器上的邮件。&lt;/p&gt; &lt;p&gt;POP 协议支持“离线”邮件处理。其具体过程是：邮件发送到服务器上，电子邮件客户端调用邮件客户机程序以连接服务器，并下载所有未阅读的电子邮件。这种离线访问模式是一种存储转发服务，将邮件从邮件服务器端送到个人终端机器上，一般是 PC机或 MAC。一旦邮件发送到 PC 机或 MAC上，邮件服务器上的邮件将会被删除。但目前的POP3邮件服务器大都可以“只下载邮件，服务器端并不删除”，也就是改进的POP3协议。&lt;/p&gt; &lt;p&gt;** SMTP 和 POP3 是最初的俩个协议，随着邮件的不断发展后来又增加了两个协议：**&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;IMAP 协议&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;全称 Internet Mail Access Protocol（交互式邮件存取协议），IMAP 是斯坦福大学在1986年开发的研发的一种邮件获取协议，即交互式邮件存取协议，它是跟 POP3 类似邮件访问标准协议之一。不同的是，开启了 IMAP 后，在电子邮件客户端收取的邮件仍然保留在服务器上，同时在客户端上的操作都会反馈到服务器上，如：删除邮件，标记已读等，服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱，看到的邮件以及状态都是一致的。&lt;/p&gt; &lt;p&gt;IMAP 的一个与 POP3 的区别是：IMAP 它只下载邮件的主题，并不是把所有的邮件内容都下载下来，而是你邮箱当中还保留着邮件的副本，没有把你原邮箱中的邮件删除，你用邮件客户软件阅读邮件时才下载邮件的内容。较好支持这两种协议的邮件客户端有：Foxmail、Outlook 等。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;Mime 协议&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;由于 SMTP 这个协议开始是基于纯 ASCⅡ文本的，在二进制文件上处理得并不好。后来开发了用来编码二进制文件的标准，如 MIME，以使其通过 SMTP 来传输。今天，大多数 SMTP 服务器都支持 8 位 MIME 扩展，它使二进制文件的传输变得几乎和纯文本一样简单。&lt;/p&gt; &lt;p&gt;用一张图来看发送邮件过程中的协议使用：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/2bbf1ba0-bb2c-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;实线代表 neo@126.com 发送邮件给 itclub@aa.com；虚线代表 itclub@aa.com 发送邮件给 neo@126.com&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;邮件发送流程&lt;/h3&gt; &lt;p&gt;&lt;img src="http://www.ityouknow.com/assets/images/2018/springboot/send_mail_process.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;发信人在用户代理上编辑邮件，并写清楚收件人的邮箱地址；&lt;/li&gt; &lt;li&gt;用户代理根据发信人编辑的信息，生成一封符合邮件格式的邮件；&lt;/li&gt; &lt;li&gt;用户代理把邮件发送到发信人的的邮件服务器上，邮件服务器上面有一个缓冲队列，发送到邮件服务器上面的邮件都会加入到缓冲队列中，等待邮件服务器上的 SMTP 客户端进行发送；&lt;/li&gt; &lt;li&gt;发信人的邮件服务器使用 SMTP 协议把这封邮件发送到收件人的邮件服务器上&lt;/li&gt; &lt;li&gt;收件人的邮件服务器收到邮件后，把这封邮件放到收件人在这个服务器上的信箱中；&lt;/li&gt; &lt;li&gt;收件人使用用户代理来收取邮件。首先用户代理使用 POP3 协议来连接收件人所在的邮件服务器，身份验证成功后，用户代理就可以把邮件服务器上面的收件人邮箱里面的邮件读取出来，并展示给收件人。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这就是邮件发送的一个完整流程。&lt;/p&gt; &lt;h3&gt;简单使用&lt;/h3&gt; &lt;p&gt;最早期的时候使用 JavaMail 相关 API 来开发，需要自己去封装消息体，代码量比较庞大；后来 Spring 推出了 JavaMailSender 简化了邮件发送过程，JavaMailSender 提供了强大的邮件发送功能，可支持各种类型的邮件发送。&lt;/p&gt; &lt;p&gt;现在 Spring Boot 在 JavaMailSender 的基础上又进行了封装，就有了现在的 spring-boot-starter-mail，让邮件发送流程更加简洁和完善。下面给大家介绍如何使用 Spring Boot 发送邮件。&lt;/p&gt; &lt;h4&gt;1、pom 包配置&lt;/h4&gt; &lt;p&gt;引入加 spring-boot-starter-mail 依赖包：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;     &amp;lt;dependency&amp;gt;          &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;spring-boot-starter-mail&amp;lt;/artifactId&amp;gt;     &amp;lt;/dependency&amp;gt;  &amp;lt;/dependencies&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;2、配置文件&lt;/h4&gt; &lt;p&gt;在 &lt;code&gt;application.properties&lt;/code&gt; 中添加邮箱配置，不同的邮箱参数稍有不同，下面列举几个常用邮箱配置：&lt;/p&gt; &lt;p&gt;163邮箱配置：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring.mail.host=smtp.163.com //邮箱服务器地址 spring.mail.username=xxx@oo.com //用户名 spring.mail.password=xxyyooo    //密码 spring.mail.default-encoding=UTF-8  //超时时间，可选 spring.mail.properties.mail.smtp.connectiontimeout=5000   spring.mail.properties.mail.smtp.timeout=3000 spring.mail.properties.mail.smtp.writetimeout=5000 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;126 邮箱配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring.mail.host=smtp.126.com spring.mail.username=yourEmail@126.com spring.mail.password=yourPassword spring.mail.default-encoding=UTF-8 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;qq 邮箱配置如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring.mail.host=smtp.qq.com spring.mail.username=ityouknow@qq.com spring.mail.password=yourPassword spring.mail.default-encoding=UTF-8 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;注意：测试时需要将 &lt;code&gt;spring.mail.username&lt;/code&gt; 和 &lt;code&gt;spring.mail.password&lt;/code&gt; 改成自己邮箱对应的登录名和密码，这里的密码不是邮箱的登录密码，是开启 POP3 之后设置的客户端授权密码。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;这里以 126 为邮件举例，有两个地方需要邮箱中设置：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;开启 POP3/SMTP 服务、IMAP/SMTP 服务&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/81b57c70-bb2c-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;p&gt;图片下方会有 smtp 等相关信息的配置提示。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;开通设置客户端授权密码&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/901b1810-bb2c-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;p&gt;设置客户端授权密码一般需求手机验证码验证。&lt;/p&gt; &lt;h4&gt;3、文本邮件发送&lt;/h4&gt; &lt;p&gt;Spring 已经帮我们内置了 JavaMailSender，直接在项目中引用即可。我们封装一个 MailService 类来实现普通的邮件发送方法。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Component public class MailServiceImpl implements MailService{      private final Logger logger = LoggerFactory.getLogger(this.getClass());      @Autowired     private JavaMailSender mailSender;      @Value(&amp;quot;${spring.mail.username}&amp;quot;)     private String from;      @Override     public void sendSimpleMail(String to, String subject, String content) {         SimpleMailMessage message = new SimpleMailMessage();         message.setFrom(from);         message.setTo(to);         message.setSubject(subject);         message.setText(content);          try {             mailSender.send(message);             logger.info(&amp;quot;简单邮件已经发送。&amp;quot;);         } catch (Exception e) {             logger.error(&amp;quot;发送简单邮件时发生异常！&amp;quot;, e);         }      } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;文本邮件抄送使用：&lt;code&gt;message.copyTo(copyTo)&lt;/code&gt; 来实现。&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;from，即为邮件发送者，一般设置在配置文件中&lt;/li&gt; &lt;li&gt;to，邮件接收者，此参数可以为数组，同时发送多人&lt;/li&gt; &lt;li&gt;subject，邮件主题&lt;/li&gt; &lt;li&gt;content，邮件的主体&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;邮件发送者 &lt;code&gt;from&lt;/code&gt; 一般采用固定的形式写到配置文件中。&lt;/p&gt; &lt;h4&gt;4、编写 test 类进行测试&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;@RunWith(SpringRunner.class) @Spring BootTest public class MailServiceTest {      @Autowired     private MailService MailService;      @Test     public void testSimpleMail() throws Exception {         mailService.sendSimpleMail(&amp;quot;ityouknow@126.com&amp;quot;,&amp;quot;这是一封简单邮件&amp;quot;,&amp;quot;大家好，这是我的第一封邮件！&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;稍微等待几秒，就可以在邮箱中找到此邮件内容了。至此一个简单的文本邮件发送就完成了。&lt;/p&gt; &lt;p&gt;&lt;img src="http://www.ityouknow.com/assets/images/2017/chat/simpeMail.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h3&gt;富文本邮件&lt;/h3&gt; &lt;p&gt;在日常使用的过程中，我们通常在邮件中加入图片或者附件来丰富邮件的内容，下面讲介绍如何使用 Spring Boot 来发送富文本邮件。&lt;/p&gt; &lt;h4&gt;发送 HTML 格式邮件&lt;/h4&gt; &lt;p&gt;邮件发送支持以 HTML 语法去构建自定义的邮件格式，Spring Boot 支持使用 HTML 发送邮件。&lt;/p&gt; &lt;p&gt;我们在 MailService 中添加支持 HTML 邮件发送的方法.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void sendHtmlMail(String to, String subject, String content) {     MimeMessage message = mailSender.createMimeMessage();      try {         //true 表示需要创建一个 multipart message         MimeMessageHelper helper = new MimeMessageHelper(message, true);         helper.setFrom(from);         helper.setTo(to);         helper.setSubject(subject);         helper.setText(content, true);          mailSender.send(message);         logger.info(&amp;quot;html邮件发送成功&amp;quot;);     } catch (MessagingException e) {         logger.error(&amp;quot;发送html邮件时发生异常！&amp;quot;, e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;富文本邮件抄送使用：&lt;code&gt;helper.addCc(cc)&lt;/code&gt; 来实现。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;和文本邮件发送代码对比，富文本邮件发送使用 MimeMessageHelper 类。MimeMessageHelper 支持发送复杂邮件模板，支持文本、附件、HTML、图片等，接下来会一一使用到。&lt;/p&gt; &lt;p&gt;在测试类中构建 HTML 内容，测试发送&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Test public void testHtmlMail() throws Exception {     String content=&amp;quot;&amp;lt;html&amp;gt;\n&amp;quot; +             &amp;quot;&amp;lt;body&amp;gt;\n&amp;quot; +             &amp;quot;    &amp;lt;h3&amp;gt;hello world ! 这是一封html邮件!&amp;lt;/h3&amp;gt;\n&amp;quot; +             &amp;quot;&amp;lt;/body&amp;gt;\n&amp;quot; +             &amp;quot;&amp;lt;/html&amp;gt;&amp;quot;;     mailService.sendHtmlMail(&amp;quot;ityouknow@126.com&amp;quot;,&amp;quot;这是一封HTML邮件&amp;quot;,content); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;邮件内容大写了一段话，下面为接收到的效果：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/c623f300-bb2c-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;p&gt;由此我们发现发送 HTML 邮件，就是需要拼接一段 HTML 的 String 字符串交给 MimeMessageHelper 来处理，最后由邮件客户端负责渲染显示内容。&lt;/p&gt; &lt;h4&gt;发送带附件的邮件&lt;/h4&gt; &lt;p&gt;在 MailService 添加 sendAttachmentsMail 方法，发送带附件的邮件主要是使用 FileSystemResource 对文件进行封装，在添加到 MimeMessageHelper 中。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void sendAttachmentsMail(String to, String subject, String content, String filePath){     MimeMessage message = mailSender.createMimeMessage();      try {         MimeMessageHelper helper = new MimeMessageHelper(message, true);         helper.setFrom(from);         helper.setTo(to);         helper.setSubject(subject);         helper.setText(content, true);          FileSystemResource file = new FileSystemResource(new File(filePath));         String fileName = file.getFilename();         helper.addAttachment(fileName, file);         //helper.addAttachment(&amp;quot;test&amp;quot;+fileName, file);          mailSender.send(message);         logger.info(&amp;quot;带附件的邮件已经发送。&amp;quot;);     } catch (MessagingException e) {         logger.error(&amp;quot;发送带附件的邮件时发生异常！&amp;quot;, e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;添加多个附件可以使用多条 &lt;code&gt;helper.addAttachment(fileName, file)&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;在测试类中添加测试方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Test public void sendAttachmentsMail() {     String filePath=&amp;quot;e:\\temp\\fastdfs-client-java-5.0.0.jar&amp;quot;;     mailService.sendAttachmentsMail(&amp;quot;ityouknow@126.com&amp;quot;, &amp;quot;主题：带附件的邮件&amp;quot;, &amp;quot;有附件，请查收！&amp;quot;, filePath); } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;附件可以是图片、压缩包、Word 等任何文件，但是邮件厂商一般都会对附件大小有限制，太大的附件建议使用网盘上传后，在邮件中给出链接。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;效果图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/f7201470-bb2c-11e8-a289-893b174e4e15.png" alt="enter image description here" title="enter image description here" /&gt;&lt;/p&gt; &lt;h4&gt;发送带静态资源的邮件&lt;/h4&gt; &lt;p&gt;邮件中的静态资源一般指图片，在 MailService 添加 sendInlineResourceMail 方法。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId){     MimeMessage message = mailSender.createMimeMessage();      try {         MimeMessageHelper helper = new MimeMessageHelper(message, true);         helper.setFrom(from);         helper.setTo(to);         helper.setSubject(subject);         helper.setText(content, true);          FileSystemResource res = new FileSystemResource(new File(rscPath));         helper.addInline(rscId, res);          mailSender.send(message);         logger.info(&amp;quot;嵌入静态资源的邮件已经发送。&amp;quot;);     } catch (MessagingException e) {         logger.error(&amp;quot;发送嵌入静态资源的邮件时发生异常！&amp;quot;, e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在测试类中添加测试方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Test public void sendInlineResourceMail() {     String rscId = &amp;quot;neo006&amp;quot;;     String content=&amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;这是有图片的邮件：&amp;lt;img src=\'cid:&amp;quot; + rscId + &amp;quot;\' &amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;;     String imgPath = &amp;quot;e:\\temp\\weixin.jpg&amp;quot;;      mailService.sendInlineResourceMail(&amp;quot;ityouknow@126.com&amp;quot;, &amp;quot;主题：这是有图片的邮件&amp;quot;, content, imgPath, rscId); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;添加多个图片可以使用多条 &lt;code&gt;&amp;lt;img src='cid:&amp;quot; + rscId + &amp;quot;' &amp;gt;&lt;/code&gt; 和 &lt;code&gt;helper.addInline(rscId, res)&lt;/code&gt; 来实现&lt;/p&gt; &lt;p&gt;效果图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/2022-04-01-9.44.01.png" alt="截屏2022-04-01 下午9.44.01" title="截屏2022-04-01 下午9.44.01" /&gt;&lt;/p&gt; &lt;p&gt;以上是邮件发送的基础服务，已演示支持各种类型邮件。&lt;/p&gt; &lt;h3&gt;邮件系统&lt;/h3&gt; &lt;p&gt;如果只是想在系统中做一个邮件工具类的话，以上的内容基本就可以满足要求了。要做成一个邮件系统的话还需要考虑以下几方面：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;对外提供发送邮件的服务接口&lt;/li&gt; &lt;li&gt;固定格式邮件是否考虑使用模板&lt;/li&gt; &lt;li&gt;发送邮件时出现网络错误，是否考虑适当的重试机制&lt;/li&gt; &lt;li&gt;邮件系统是否考虑异步化，提升服务响应时间&lt;/li&gt; &lt;li&gt;是否开发邮件后台管理系统，开发出对应的管理软件，通过页面发送邮件，统计发送邮件成功率等数据。&lt;/li&gt; &lt;li&gt;常见异常处理措施&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;对外提供接口&lt;/h4&gt; &lt;p&gt;作为一个独立的邮件系统，需要对外提供接口调用，我们以简单文本邮件为例做个演示：&lt;/p&gt; &lt;p&gt;首先需要定义个实例返回对象：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class MailResult {     private String rspCode;     private String rspMsg;      public MailResult() {         this.rspCode = &amp;quot;00&amp;quot;;         this.rspMsg = &amp;quot;发送成功&amp;quot;;     }     //省略 setter/getter } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;默认成功的返回码为：00，返回消息为：发送成功。&lt;/p&gt; &lt;p&gt;创建一个 MailController 类对外提供 HTTP 请求接口。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@RestController public class MailController {     private final Logger logger = LoggerFactory.getLogger(this.getClass());     @Resource     private MailService mailService;      @RequestMapping(&amp;quot;/sendSimpleMail&amp;quot;)     public MailResult sendSimpleMail(String to, String subject, String content) {         MailResult result=new MailResult();         if(StringUtils.isEmpty(to) || !to.contains(&amp;quot;@&amp;quot;)){             result.setRspCode(&amp;quot;01&amp;quot;);             result.setRspCode(&amp;quot;手机人邮件格式不正确&amp;quot;);         }         if(StringUtils.isEmpty(content) ){             result.setRspCode(&amp;quot;03&amp;quot;);             result.setRspCode(&amp;quot;邮件正文不能为空&amp;quot;);         }        try {             mailService.sendSimpleMail(to,subject,content);             logger.info(&amp;quot;简单邮件已经发送。&amp;quot;);         } catch (Exception e) {             result.setRspCode(&amp;quot;04&amp;quot;);             result.setRspCode(&amp;quot;邮件发送出现异常&amp;quot;);             logger.error(&amp;quot;sendSimpleMail Exception &amp;quot;, e);         }         return result;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;外部请求过来时首先进行参数校验，如果参数有误返回请求；发送邮件出现异常时返回错误，正常情况下返回 00；注意在 Service 层如果对异常信息进行了捕获的话，需要将异常信息抛到上层。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;try {     mailSender.send(message);     logger.info(&amp;quot;简单邮件已经发送。&amp;quot;); } catch (Exception e) {     logger.error(&amp;quot;发送简单邮件时发生异常！&amp;quot;, e);     throw e; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;类似上述代码。&lt;/p&gt; &lt;p&gt;按照这个思路也可以提供发送带图片、带附件的邮件，同时也可以封装发送多人邮件，群发邮件等复杂情况。&lt;/p&gt; &lt;h4&gt;邮件模板&lt;/h4&gt; &lt;p&gt;通常我们使用邮件发送服务的时候，都会有一些固定的场景，比如重置密码、注册确认等，给每个用户发送的内容可能只有小部分是变化的。所以，很多时候我们会使用模板引擎来为各类邮件设置成模板，这样我们只需要在发送时去替换变化部分的参数即可。&lt;/p&gt; &lt;p&gt;我们会经常收到这样的邮件：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;尊敬的 neo 用户：                恭喜您注册成为xxx网的用户,同时感谢您对xxx的关注与支持并欢迎您使用xx的产品与服务。               ... &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;邮件正文只有 &lt;code&gt;neo&lt;/code&gt; 这个用户名在变化，邮件其它内容均不变，如果每次发送邮件都需拼接 HTML 代码，程序不够优雅，并且每次邮件正文有变化都需修改代码非常不方便。因此对于这类邮件，都建议做成邮件模板来处理，模板的本质很简单，就是在模板中替换变化的参数，转换为 HTML 字符串即可，这里以 Thymeleaf 为例来演示。&lt;/p&gt; &lt;p&gt;Thymeleaf 是 Spring 官方推荐的前端模板引擎，类似 Velocity、FreeMarker 等模板引擎，相较与其他的模板引擎，Thymeleaf 开箱即用的特性。它提供标准和 Spring 标准两种方言，可以直接套用模板实现 JSTL、 OGNL 表达式效果，避免每天套模板、该 Jstl、改标签的困扰。Thymeleaf 在有网络和无网络的环境下皆可运行，即它可以让美工在浏览器查看页面的静态效果，也可以让程序员在服务器查看带数据的动态页面效果。&lt;/p&gt; &lt;p&gt;下面我们来演示使用 Thymeleaf 制作邮件模板。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、添加依赖包&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;     &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;spring-boot-starter-thymeleaf&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;2、在 &lt;code&gt;resorces/templates&lt;/code&gt; 下创建 &lt;code&gt;emailTemplate.html&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;emailTemplate.html&lt;/code&gt; 文件内容即为邮件的正文内容模板。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&amp;quot;zh&amp;quot; xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;gt;     &amp;lt;head&amp;gt;         &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;/&amp;gt;         &amp;lt;title&amp;gt;邮件模板&amp;lt;/title&amp;gt;     &amp;lt;/head&amp;gt;     &amp;lt;body&amp;gt;         您好,感谢您的注册，这是一封验证邮件，请点击下面的链接完成注册，感谢您的支持&amp;lt;br/&amp;gt;         &amp;lt;a href=&amp;quot;#&amp;quot; th:href=&amp;quot;@{http://www.ityouknow.com/register/{id}(id=${id}) }&amp;quot;&amp;gt;激活账号&amp;lt;/a&amp;gt;     &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们发现上述的模板中只有 &lt;code&gt;id&lt;/code&gt; 是一个动态的值，发送过程中会根据传入的 &lt;code&gt;id&lt;/code&gt; 值来替换链接中的 &lt;code&gt;{id}&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3、解析模板并发送&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Test public void sendTemplateMail() {     //创建邮件正文     Context context = new Context();     //设置模板需要替换的参数     context.setVariable(&amp;quot;id&amp;quot;, &amp;quot;006&amp;quot;);     //使用 templateEngine 替换掉动态参数生产出最后的 HTML 内容。     String emailContent = templateEngine.process(&amp;quot;emailTemplate&amp;quot;, context);     //最后调用 sendHtmlMail 发送邮件     mailService.sendHtmlMail(&amp;quot;ityouknow@126.com&amp;quot;,&amp;quot;主题：这是模板邮件&amp;quot;,emailContent); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们发现最后调用的还是 sendHtmlMail 的方法，邮件模板的作用只是处理 HTML 生成部分，通过 Thymeleaf 模板引擎解析固定的模板，再更具参数来动态替换其中的变量，最后通过前面的 HTML 发送的方法发送邮件。&lt;/p&gt; &lt;p&gt;效果图如下：&lt;/p&gt; &lt;p&gt;&lt;img src="http://www.ityouknow.com/assets/images/2017/chat/templateMail.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;点击“激活账号”跳转的链接为：http://www.ityouknow.com/register/006&lt;/p&gt; &lt;h4&gt;发送失败&lt;/h4&gt; &lt;p&gt;因为各种原因，总会有邮件发送失败的情况，比如：邮件发送过于频繁、网络异常等。在出现这种情况的时候，我们一般会考虑重新重试发送邮件，会分为以下几个步骤来实现：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;接收到发送邮件请求，首先记录请求并且入库。&lt;/li&gt; &lt;li&gt;调用邮件发送接口发送邮件，并且将发送结果记录入库。&lt;/li&gt; &lt;li&gt;启动定时系统扫描时间段内，未发送成功并且重试次数小于3次的邮件，进行再次发送.&lt;/li&gt; &lt;li&gt;重新发送邮件的时间，建议以 2 的次方间隔时间，比如：2、4、8、16 ...&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;常见的错误返回码：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;421 HL:ICC 该IP同时并发连接数过大，超过了网易的限制，被临时禁止连接。&lt;/li&gt; &lt;li&gt;451 Requested mail action not taken: too much fail authentication 登录失败次数过多，被临时禁止登录。请检查密码与帐号验证设置&lt;/li&gt; &lt;li&gt;553 authentication is required，密码配置不正确&lt;/li&gt; &lt;li&gt;554 DT:SPM 发送的邮件内容包含了未被许可的信息，或被系统识别为垃圾邮件。请检查是否有用户发送病毒或者垃圾邮件；&lt;/li&gt; &lt;li&gt;550 Invalid User 请求的用户不存在&lt;/li&gt; &lt;li&gt;554 MI:STC 发件人当天内累计邮件数量超过限制，当天不再接受该发件人的投信。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;如果使用一个邮箱频繁发送相同内容邮件，也会被认定为垃圾邮件，报 554 DT:SPM 错误&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如果使用网易邮箱可以查看这里的提示：&lt;a href="http://help.163.com/09/1224/17/5RAJ4LMH00753VB8.html" target="_blank"&gt;企业退信的常见问题？&lt;/a&gt;&lt;/p&gt; &lt;h4&gt;其它&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;异步发送&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;很多时候邮件发送并不是主业务必须关注的结果，比如通知类、提醒类的业务可以允许延时或者失败。这个时候可以采用异步的方式来发送邮件，加快主交易执行速度。在实际项目中可以采用消息中间件 MQ 发送邮件，具体做法是创建一个邮件发送的消息队列，在业务中有需要用到邮件发送功能时，给对应消息队列按照规定参数发送一条消息，邮件系统监听此队列，当有消息过来时，处理邮件发送的逻辑。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;管理后台&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;考虑做一个完善的邮件系统，可以设计一个独立的邮件管理后台，不但可以让系统之间调用时使用，也可以提供图形化界面让公司的运营、市场部的同事来发送邮件，查询邮件的发送进度，统计邮件发送成功率。也可以设置一些代码钩子，统计用户点击固定链接次数，方便公司营销人员监控邮件营销转化率。&lt;/p&gt; &lt;p&gt;一个非常完善的邮件系统需要考虑的因素非常多，比如是否设置白名单、黑名单来做邮件接收人的过滤机制，是否给用户提供邮件退订的接口等。因此在初期邮件发送的基本功能完成之后，再结合公司业务，快速迭代的逐步完善邮件系统，是一个推荐的做法。&lt;/p&gt; &lt;h3&gt;总结&lt;/h3&gt; &lt;p&gt;使用 Spring Boot 集成发送邮件的功能非常简单，只需要简单编码就可以实现发送普通文本邮件、带附件邮件、HTML 格式邮件、带图片邮件等。如果需要做成一个邮件系统还需要考虑很多因素，比如：邮箱发送失败重试机制、防止邮件被识别为垃圾邮件，固定时间内发送邮件的限制等。在微服务架构中，常常将一些基础功能下沉下来，作为独立的服务来使用，邮件系统作为平台的基础功能，特别适合做为独立的微服务来支持整个系统。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;a href="https://github.com/ityouknow/spring-boot-leaning/tree/gitbook_column2.0" target="_blank"&gt;点击这里下载源码&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;参考：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;em&gt;https://www.geekpark.net/news/214789&lt;/em&gt;&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Fri, 01 Apr 2022 13:45:00 GMT</pubDate>
    </item>
    <item>
      <title>接口测试工具 Postman 使用实践</title>
      <link>https://maruifu.cn/article/225</link>
      <content:encoded>&lt;h3&gt;一、接口定义&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;软件不同部分之间的交互接口。通常就是所谓的 API――应用程序编程接口，其表现的形式是源代码。   ——  [ 百度百科 ]&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;我们常说的接口一般指两种：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;API：应用程序编程接口。程序间的接口&lt;/li&gt; &lt;li&gt;GUI：图形用户界面。人与程序的接口&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;这里我们所说的接口特指 API 接口。API 接口定义：对协议进行定义的引用类型。&lt;/p&gt; &lt;p&gt;好多公司开发人员分前后端，他们之间如何配合工作的，就是其中一方定义接口，另一方来调用接口，以实现预期功能。&lt;/p&gt; &lt;h3&gt;二、接口的分类&lt;/h3&gt; &lt;h4&gt;1. 接口分类&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;HTTP 接口&lt;/li&gt; &lt;li&gt;Webservice 接口&lt;/li&gt; &lt;li&gt;RESTful 接口&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;WebService 接口是走 soap 协议，请求报文和返回报文都是 xml 格式，通过 SoapUI 工具进行测试；&lt;/p&gt; &lt;p&gt;HTTP API 接口走 HTTP 协议，通过路径来区分调用的方法，请求报文入参有多种形式，返回报文一般为 json 串，最常见的是 get 和 post 方法。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/image-20220401211423619.png" alt="image-20220401211423619" title="image-20220401211423619" /&gt;&lt;/p&gt; &lt;h3&gt;三、为何要进行接口测试&lt;/h3&gt; &lt;h4&gt;1. 接口测试必要性&lt;/h4&gt; &lt;p&gt;当今的系统复杂度不断上升，传统的测试方法成本急剧增加且测试效率大幅下降，所以就要做接口测试。&lt;/p&gt; &lt;p&gt;同时，接口测试相对容易实现自动化持续集成，且相对 UI 自动化也比较稳定，可以减少人工回归测试人力成本与时间，缩短测试周期，支持后端快速发版需求。&lt;/p&gt; &lt;p&gt;接口持续集成是为什么能低成本高收益的根源。现在很多系统前后端架构是分离的，从安全层面来说，只依赖前端进行限制已经完全不能满足系统的安全要求（绕过前面实在太容易）， 需要后端同样进行控制，在这种情况下就需要从接口层面进行验证。&lt;/p&gt; &lt;p&gt;前后端传输、日志打印等信息是否加密传输也是需要验证的，特别是涉及到用户的隐私信息，如身份证，银行卡等。&lt;/p&gt; &lt;h4&gt;2. 接口测试原理&lt;/h4&gt; &lt;p&gt;模拟客户端向服务器发送请求报文，服务器接收请求报文后对相应的报文做处理并向客户端返回应答，客户端再接收应答的一个过程。&lt;/p&gt; &lt;h4&gt;3. 接口测试范围&lt;/h4&gt; &lt;p&gt;接口的功能、性能、安全性。重点关注数据的交换，传递和控制管理过程，还包括处理的次数。&lt;/p&gt; &lt;p&gt;接口测试对象是接口，但随着系统复杂度越来越高，接口越来越多，完全覆盖是一件很困难的事情。&lt;/p&gt; &lt;p&gt;通常情况下主要测试最外层的两类接口：数据进入系统的接口（调用外部系统的参数为本系统使用）、数据流出系统接口（验证系统处理后的数据是否正常）&lt;/p&gt; &lt;h3&gt;四、接口文档示例&lt;/h3&gt; &lt;h4&gt;1. 接口文档应该包括哪几部分？&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;接口说明&lt;/li&gt; &lt;li&gt;调用的 url&lt;/li&gt; &lt;li&gt;请求方法（get、post）&lt;/li&gt; &lt;li&gt;请求参数，参数类型、请求参数说明&lt;/li&gt; &lt;li&gt;返回参数说明&lt;/li&gt; &lt;li&gt;返回示例&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/2022-04-01-9.16.58.png" alt="截屏2022-04-01 下午9.16.58" title="截屏2022-04-01 下午9.16.58" /&gt;2. 示例&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/2022-04-01-9.17.55.png" alt="截屏2022-04-01 下午9.17.55" title="截屏2022-04-01 下午9.17.55" /&gt;&lt;/p&gt; &lt;p&gt;注：上图接口文档工具为 ShowDoc&lt;/p&gt; &lt;h3&gt;五、Postman 工具简介&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/2022-04-01-9.19.36.png" alt="截屏2022-04-01 下午9.19.36" title="截屏2022-04-01 下午9.19.36" /&gt;&lt;/p&gt; &lt;h4&gt;1. Sidebar 侧边栏&lt;/h4&gt; &lt;p&gt;Postman 侧边栏允许你查找、管理请求和集合。侧边栏分为两个主要的选项卡，包括历史和集合选项卡。可以拖动右边的边来调整侧边栏的宽度。侧边栏也可以隐藏到小屏幕（标题栏 view—&amp;gt;toggle side bar）。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（1）历史选项卡&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通过 Postman 应用程序发送的每个请求都保存在侧边栏的 History 选项卡中。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（2）集合选项卡&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在侧栏中创建和管理集合选项卡的集合。&lt;/p&gt; &lt;h4&gt;2. Header toolbar&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212212702.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;Postman 的顶部工具栏包含以下选项：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;新建按钮——可以新建请求，集合，环境等&lt;/li&gt; &lt;li&gt;运行按钮-打开集合运行页面&lt;/li&gt; &lt;li&gt;导入按钮——导入 Postman 文件、文件夹、form link 等&lt;/li&gt; &lt;li&gt;新窗口图标-打开一个新的 tab 页、新的窗口、新的 runner 等&lt;/li&gt; &lt;li&gt;构建器和团队库选项卡——在请求生成器和 Team Library 视图之间切换&lt;/li&gt; &lt;li&gt;抓取 API 请求图标——使用 postman 抓取 API 请求&lt;/li&gt; &lt;li&gt;同步状态图标——同步 API 请求图标&lt;/li&gt; &lt;li&gt;用户下拉——管理集合链接和你的个人资料或登录 / 登出，你的 Postman 帐户&lt;/li&gt; &lt;li&gt;开放 API 集合（点击打开一个网址）&lt;/li&gt; &lt;li&gt;通知图标-接收通知或广播&lt;/li&gt; &lt;li&gt;设置图标——管理 Postman 应用程序设置，并找到其他支持资源&lt;/li&gt; &lt;li&gt;❤——分享按钮&lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;3. Builder&lt;/h4&gt; &lt;p&gt;Postman 通过选项卡布局，用于在构建器中发送和管理 API 请求。上半部分是请求构建器，下半部分是响应查看器。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Cookies——管理 cookie 模式是通过点击 cookie 链接访问的。该特性允许你管理与请求相关的 cookie。&lt;/li&gt; &lt;li&gt;Code——生成的代码片段模式通过保存按钮下面的最右边的 Code 链接。该特性允许你生成与请求相关的代码片段，该请求支持 20 多种语言（http、java、go 等语言）&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212245769.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h4&gt;4. Console&lt;/h4&gt; &lt;p&gt;Postman 有两个控制台，可以帮助我们了解系统后台到底发生了什么。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Postman Console——包含 HTTP 请求和响应的运行日志。来自脚本的日志消息 (如在 console. Log 中)。这个功能只能在 Postman 的本地应用中使用。&lt;/li&gt; &lt;li&gt;DevTools Console——可以在开发期间记录诊断信息。&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;六、借助 Postman 完成 HTTP 请求接口测试&lt;/h3&gt; &lt;h4&gt;1. 借助 Postman Echo 演示下各种请求的构建方法&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;（1）Get 请求&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;https://postman-echo.com/get?foo1=bar1&amp;amp;foo2=bar2&lt;/p&gt; &lt;p&gt;HTTP GET 请求方法是从服务器检索数据。数据由惟一 URI(统一资源标识符) 标识。GET 请求可以使用 “查询字符串参数” 将参数传递给服务器。例如，在下列请求中，http://example.com/hi/there?hand=wave，参数 “hand” 的值等于 “wave”。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212310157.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（2）POST：URI 传参&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212409076.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（3）POST：Form-data 传参&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212412934.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（4）POST：x-www-form-urlencoded 传参&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212417732.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（5）POST：raw 传参&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212422327.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（6）POST：binary 传参&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212550577.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;（7）Authentication Method——权限认证方法&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;GET Basic Auth&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212557237.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;增加 auth 信息：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212427734.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;DigestAuth&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Hawk Auth&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;OAuth1.0(verify signature)&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;（8）Headers——添加 header&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212533649.null.jpg" alt="Image" title="Image" /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;2. 接下来，我们拿个开放 API 来演示下单一接口测试流程&lt;/h4&gt; &lt;p&gt;示例 API：https://developers.douban.com/wiki/?title=book_v2#get_book&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212530383.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;步骤一：使用 Postman 工具发送该 Get 请求，如下图。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212525926.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;步骤二：添加测试。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212522107.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;上图针对该 API 添加了 3 个测试：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;要求响应时间小于 200ms&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;要求 status code 等于 200&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;要求 Response body 中包含字符串 “金庸”&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;注：当然你还可以增加更多的测试点。&lt;/p&gt; &lt;h3&gt;七、Postman + Newman + Jenkins 实现接口自动化测试&lt;/h3&gt; &lt;h4&gt;1. 准备工作（具体步骤参考附件文档-作者提供）&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;（1）安装 Newman 工具&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;安装 Node.js&lt;/li&gt; &lt;li&gt;安装 Newman&lt;/li&gt; &lt;li&gt;查看 Newman 命令&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;（2）部署 Jenkins&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;2. 将接口保存到集合&lt;/h4&gt; &lt;p&gt;点击 Save 按钮，将接口保存到一个集合（可以保存到一个现有集合中或者新建一个集合），如下图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212517179.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h4&gt;3. 将集合保存到本地&lt;/h4&gt; &lt;p&gt;将集合保存到本地，文件为 .json 格式，如下图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212513539.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h4&gt;4. 命令行通过 Newman 运行集合&lt;/h4&gt; &lt;p&gt;（1）打开命令行窗口，运行如下命令：&lt;/p&gt; &lt;p&gt;D:\git-local&amp;gt;newman run MyCollection1.postman_collection.json -g globals.postman_globals1.json&lt;/p&gt; &lt;p&gt;（2）执行结果如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212510416.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;可以看到，其中两条断言 passed，一条断言 failed，失败的原因是，我们期望接口响应时间小于 200 ms，但是本次接口请求响应时间是 270 ms。&lt;/p&gt; &lt;h4&gt;5. 通过 Jenkins 调用 Newman，执行接口测试&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212506261.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;执行一次构建，构建失败（上面的断言失败，我们并未修复），查看构建失败原因。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212501531.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h4&gt;6. 假设开发修复了接口 bug&lt;/h4&gt; &lt;p&gt;接口响应时间减少了，我们需要回归测试。（我们将断言响应小于 200 ms，修改成 1000 ms，让断言 passed）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212457923.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;h4&gt;7. 演示一个如何调用 data file 参数化用例&lt;/h4&gt; &lt;p&gt;我这里有一个集合，3 个接口，第一个接口为登录接口，第二个接口为获取登录用户信息接口，第三个接口为修改密码接口。登录接口如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212453194.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;测试脚本如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212447985.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;参数化 json 文件内容如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[{   &amp;quot;loginName&amp;quot;: &amp;quot;duzl&amp;quot;,  &amp;quot;password&amp;quot;: &amp;quot;admin123&amp;quot;,  &amp;quot;verifyCode&amp;quot;: &amp;quot;adf&amp;quot;,  &amp;quot;value&amp;quot;: &amp;quot;/index&amp;quot; }, {  &amp;quot;loginName&amp;quot;: &amp;quot;duzl&amp;quot;,  &amp;quot;password&amp;quot;: &amp;quot;admin&amp;quot;,  &amp;quot;verifyCode&amp;quot;: &amp;quot;adf&amp;quot;,  &amp;quot;value&amp;quot;: &amp;quot; 账号或密码错误 &amp;quot; }, {  &amp;quot;loginName&amp;quot;: &amp;quot;duzl&amp;quot;,  &amp;quot;password&amp;quot;: &amp;quot;&amp;quot;,  &amp;quot;verifyCode&amp;quot;: &amp;quot;adf&amp;quot;,  &amp;quot;value&amp;quot;: &amp;quot; 参数 password 不能为空 &amp;quot; }] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212444031.null.png" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;（1）好我们调用 json 文件，执行下集合，结果如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212440944.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;结果还不错，执行了 3 次，参数都是取自用例文件（json 文件），断言也取自用例文件。&lt;/p&gt; &lt;p&gt;美中不足的是，第二个和第三个接口也跟着迭代了 3 次（这并不是我们期望的结果），这是因为集合运行器中的迭代次数是针对所有接口的设置。&lt;/p&gt; &lt;p&gt;（2）那如果，我们想第一个接口运行 3 遍，第二、三个接口只运行一遍，该如何做呢？Postman 给我们提供了一个内置方法，设置接口运行顺序&lt;code&gt;postman.setNextRequest('');&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212437801.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;注意：迭代次数从 0 开始。&lt;/p&gt; &lt;p&gt;当迭代次数 !==0 时，就停止本次迭代（意思就是，第一次迭代全运行，第二次迭代开始就不执行第二、三个接口了），好，再次运行集合，看看结果：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/04/01/null-20220401212434305.null.jpg" alt="Image" title="Image" /&gt;&lt;/p&gt; &lt;p&gt;很好，第一次迭代，执行了 3 个接口；第二、三次迭代只执行了第一个接口。&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 01 Apr 2022 13:26:00 GMT</pubDate>
    </item>
    <item>
      <title>Windows10右键添加"在此处打开命令窗口</title>
      <link>https://maruifu.cn/article/224</link>
      <content:encoded>&lt;p&gt;今天跑代码时用到了Powershell，有一个环境变量死活都找不到，无奈只好重新回到cmd命令行测试，结果立马就跑通了。 由于现在Win10默认右键只有Powershell，所以为了以后方便使用就把右键添加“在此处打开命令窗口”的代码分享出来。&lt;/p&gt; &lt;p&gt;效果显示“在此处打开命令窗口”选项，如图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/14/image-20220214100527129.png" alt="image-20220214100527129" title="image-20220214100527129" /&gt;&lt;/p&gt; &lt;h3&gt;具体步骤&lt;/h3&gt; &lt;p&gt;第一步：新建一个txt文件，命名为OpenCmdHere.txt，注意设置编码格式为ANSI 第二步：在文件中输入如下代码，并保存&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Windows Registry Editor Version 5.00  [HKEY_CLASSES_ROOT\Directory\shell\OpenCmdHere] @=&amp;quot;在此处打开命令窗口&amp;quot; &amp;quot;Icon&amp;quot;=&amp;quot;cmd.exe&amp;quot;  [HKEY_CLASSES_ROOT\Directory\shell\OpenCmdHere\command] @=&amp;quot;cmd.exe /s /k pushd \&amp;quot;%V\&amp;quot;&amp;quot;  [HKEY_CLASSES_ROOT\Directory\Background\shell\OpenCmdHere] @=&amp;quot;在此处打开命令窗口&amp;quot; &amp;quot;Icon&amp;quot;=&amp;quot;cmd.exe&amp;quot;  [HKEY_CLASSES_ROOT\Directory\Background\shell\OpenCmdHere\command] @=&amp;quot;cmd.exe /s /k pushd \&amp;quot;%V\&amp;quot;&amp;quot;  [HKEY_CLASSES_ROOT\Drive\shell\OpenCmdHere] @=&amp;quot;在此处打开命令窗口&amp;quot; &amp;quot;Icon&amp;quot;=&amp;quot;cmd.exe&amp;quot;  [HKEY_CLASSES_ROOT\Drive\shell\OpenCmdHere\command] @=&amp;quot;cmd.exe /s /k pushd \&amp;quot;%V\&amp;quot;&amp;quot;  [HKEY_CLASSES_ROOT\LibraryFolder\background\shell\OpenCmdHere] @=&amp;quot;在此处打开命令窗口&amp;quot; &amp;quot;Icon&amp;quot;=&amp;quot;cmd.exe&amp;quot;  [HKEY_CLASSES_ROOT\LibraryFolder\background\shell\OpenCmdHere\command] @=&amp;quot;cmd.exe /s /k pushd \&amp;quot;%V\&amp;quot;&amp;quot;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第三步：更改文件后缀名为reg，弹出的提示点确认。 第四步：双击OpenCmdHere.reg文件运行，弹出的提示点确认，修改注册表就大功告成了！&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 14 Feb 2022 02:06:00 GMT</pubDate>
    </item>
    <item>
      <title>群晖NAS安装gitlab</title>
      <link>https://maruifu.cn/article/223</link>
      <content:encoded>&lt;h2&gt;下载镜像&lt;/h2&gt; &lt;p&gt;docker-&amp;gt;注册表-&amp;gt;搜索gitlab&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208160902209.png" alt="image-20220208160902209" title="image-20220208160902209" /&gt;&lt;/p&gt; &lt;h2&gt;配置镜像&lt;/h2&gt; &lt;p&gt;映像-&amp;gt;选中镜像-&amp;gt;启动&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208161053204.png" alt="image-20220208161053204" title="image-20220208161053204" /&gt;&lt;/p&gt; &lt;p&gt;点击高级设置，在弹出的高级选项中，切换到卷页签，按照下面截图，设置目录。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208161902760.png" alt="image-20220208161902760" title="image-20220208161902760" /&gt;&lt;/p&gt; &lt;p&gt;这里需要添加对应的文件夹到docker目录下，可以使用File Station在docker目录下，创建gitlab目录。&lt;/p&gt; &lt;p&gt;然后在gitlab目录下，分别创建logs，config，data来存储日志、配置和数据信息文件。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208161553127.png" alt="image-20220208161553127" title="image-20220208161553127" /&gt;&lt;/p&gt; &lt;p&gt;切换端口设置页签，设置一个本地端口，这里指定80容器端口对应本地端口10080。当然也建议将其他本地端口的[自动]改为指定的端口，比如22端口对应的本地端口改为10022之类的，因为后续还要修改配置文件，让克隆地址可以正常显示端口，同时也避免自动获取而带来端口变化而导致的访问问题。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208162119655.png" alt="image-20220208162119655" title="image-20220208162119655" /&gt;&lt;/p&gt; &lt;p&gt;这时候可以直接访问地址了&lt;code&gt;IP:10080&lt;/code&gt;，不知道为什么,程序运行没问题 我访问的时候不显示，&lt;/p&gt; &lt;p&gt;后来我在反向代理哪里设置了一下 访问代理地址就没问题了！如图所示！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/08/image-20220208171010021.png" alt="image-20220208171010021" title="image-20220208171010021" /&gt;&lt;/p&gt; &lt;h2&gt;常见问题&lt;/h2&gt; &lt;h3&gt;ROOT密码&lt;/h3&gt; &lt;p&gt;登陆后发现没有让输入新密码 而是账号和密码我在&lt;code&gt;docker/config&lt;/code&gt;目录里发现一个一个&lt;code&gt;initial_root_password&lt;/code&gt;文件&lt;/p&gt; &lt;p&gt;打开一看果然记录了初始密码。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;查看Gitlab网站文档，原来GitLab 14.0开始， 把密码放在了/etc/gitlab/initial_root_password里了。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;修改域名地址&lt;/h3&gt; &lt;p&gt;我们在下载项目的时候域名不是我们想要的地址，这个时候可以修改配置文件。&lt;/p&gt; &lt;p&gt;&lt;code&gt;/docker/gitlab/config/gitlab.rb&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 32行修改以下配置 external_url 'https://&amp;lt;你的域名&amp;gt;:&amp;lt;端口&amp;gt;' 我的填写的 external_url 'https://nas.mrf.ink:10444' &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这时候也要修改启动容器的端口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/09/image-20220209094117569.png" alt="image-20220209094117569" title="image-20220209094117569" /&gt;&lt;/p&gt; &lt;p&gt;我的群晖反向代理这么填写&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/09/image-20220209094444843.png" alt="image-20220209094444843" title="image-20220209094444843" /&gt;&lt;/p&gt; &lt;h3&gt;配置电子邮件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#修改以下配置 gitlab_rails['smtp_enable'] = true                              gitlab_rails['smtp_address'] = &amp;quot;smtp.mxhichina.com&amp;quot;   gitlab_rails['smtp_port'] = 25                       gitlab_rails['smtp_user_name'] = &amp;quot;email@maruifu.cn&amp;quot;    gitlab_rails['smtp_password'] = &amp;quot;123456&amp;quot;       gitlab_rails['smtp_domain'] = &amp;quot;smtp.mxhichina.com&amp;quot;   gitlab_rails['smtp_authentication'] = &amp;quot;plain&amp;quot;     gitlab_rails['smtp_enable_starttls_auto'] = true    gitlab_rails['smtp_tls'] = false    gitlab_rails['smtp_pool'] = false   ### Email Settings                                       gitlab_rails['gitlab_email_enabled'] = true                  ##! If your SMTP server does not like the default 'From: gitlab@gitlab.example.com'                     ##! can change the 'From' with this setting.     gitlab_rails['gitlab_email_from'] = 'email@maruifu.cn'     gitlab_rails['gitlab_email_display_name'] = '小马哥'    &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt; #生效配置文件 gitlab-ctl reconfigure #进入后台 gitlab-rails console -e production 或者 gitlab-rails console #发送测试邮件 Notify.test_email('mrf_it@163.com', '标题', '内容').deliver_now &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;gitlab使用命令行修改用户密码&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;gitlab-rails console production   &amp;gt; user=User.where(username: &amp;quot;root&amp;quot;).first &amp;gt; user.password=123123 &amp;gt; user.save! &amp;gt; quit &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 09 Feb 2022 01:47:00 GMT</pubDate>
    </item>
    <item>
      <title>我做系统架构的一些原则（转载）</title>
      <link>https://maruifu.cn/article/222</link>
      <content:encoded>&lt;p&gt;工作 20 多年了，这 20 来年看到了很多公司系统架构，也看到了很多问题，在跟这些公司进行交流和讨论的时候，包括进行实施和方案比较的时候，都有很多各种方案的比较和妥协，因为相关的经历越来越多，所以，逐渐形成了自己的逻辑和方法论。今天，想写下这篇文章，把我的这些个人的经验和想法总结下来，希望能够让更多的人可以参考和借鉴，并能够做出更好的架构来。另外，我的这些思维方式和原则都针对于现有市面上众多不合理的架构和方案，所以，也算是一种“纠正”……（注意，这篇文章所说的这些架构上的原则，一般适用于相对比较复杂的业务，如果只是一些简单和访问量不大的应用，那么你可能会得出相反的结论）&lt;/p&gt; &lt;h4&gt;原则一：关注于真正的收益而不是技术本身&lt;/h4&gt; &lt;p&gt;对于软件架构来说，我觉得第一重要的是架构的收益，如果不说收 益，只是为了技术而技术，而没有任何意义。对于技术收益来说，我觉得下面这几个收益是非常重要的：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;是否可以降低技术门槛加快整个团队的开发流程&lt;/strong&gt;。能够加快整个团队的工程流程，快速发布，是软件工程一直在解决的问题，所以，系统架构需要能够进行并行开发，并行上线和并行运维，而不会让某个团队成为瓶颈点。（注：就算拖累团队的原因是组织构架，也不妨碍我们做出并行的系统架构设计）&lt;/li&gt; &lt;li&gt;&lt;strong&gt;是否可以让整个系统可以运行的更稳定&lt;/strong&gt;。要让整个系统可以运行的更为的稳定，提升整个系统的 SLA，就需要对有计划和无计划的停机做相应的解决方案（参看《&lt;a href="https://coolshell.cn/articles/17459.html" target="_blank"&gt;关于高可用的架构&lt;/a&gt;》）&lt;/li&gt; &lt;li&gt;&lt;strong&gt;是否可以通过简化和自动化降低成本&lt;/strong&gt;。最高优化的成本是人力成本，人的成本除了慢和贵，还有经常不断的 human error。如果不能降低人力成本，反而需要更多的人，那么这个架构设计一定是失败的。除此之外，是时间成本，资金成本。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;如果一个系统架构不能在上面三个事上起到作用，那就没有意义了。&lt;/p&gt; &lt;h4&gt;原则二：以应用服务和 API 为视角，而不是以资源和技术为视角&lt;/h4&gt; &lt;p&gt;国内很多公司都会有很多分工，基本上都会分成运维和开发，运维又会分成基础运维和应用运维，开发则会分成基础核心开发和业务开发。不同的分工会导致完全不同的视角和出发点。比如，基础运维和开发的同学更多的只是关注资源的利用率和性能，而应用运维和业务开发则更多关注的是应用和服务上的东西。这两者本来相关无事，但是因为分布式架构的演进，导致有一些系统已经说不清楚是基础层的还是应用层的了，比如像服务治理上的东西，里面即有底层基础技术，也需要业务的同学来配合，包括 k8s 也样，里面即有底层的如网络这样的技术，也有需要业务配合的 readniess和 liveness 这样的健康检查，以及业务应用需要 configMap 等等 ……&lt;/p&gt; &lt;p&gt;&lt;strong&gt;这些东西都让我感觉到所谓 DevOps，其实就是因为很多技术和组件已经分不清是 Dev 还是 Ops 的了，所以，需要合并 Dev和 Ops&lt;/strong&gt;。而且，整个组织和架构的优化，已经不能通过调优单一分工或是单一组件能够有很大提升的了。其需要有一种自顶向下的，整体规划，统一设计的方式，才能做到整体的提升（可以试想一下城市交通的优化，当城市规模到一定程度的时候，整体的性能你是无法通过优化几条路或是几条街区来完成的，你需要对整个城市做整体的功能体的规划才可能达到整体效率的提升）。而为了做到整体的提升，需要所有的人都要有一个统一的视角和目标，这几年来，我觉得这个目标就是——&lt;strong&gt;要站在服务和 对外API的视角来看问题，而不是技术和底层的角度。&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;原则三：选择最主流和成熟的技术&lt;/h4&gt; &lt;p&gt;技术选型是一件很重要的事，技术一旦选错，那会导致整个架构需要做调整，而对架构的调整重来都不是一件简单的事，我在过去几年内，当系统越来越复杂的时候，用户把他们的 PHP，Python, .NET，或 Node.js 的架构完全都迁移到 Java + Go 的架构上来的案例不断的发生。这个过程还是非常痛苦的，但是你没有办法，当你的系统越来越复杂，越来越大时，你就再也不能在一些玩具技术上玩了，你需要的更为工业化的技术。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;尽可能的使用更为成熟更为工业化的技术栈，而不是自己熟悉的技术栈&lt;/strong&gt;。所谓工业化的技术栈，你可以看看大多数公司使用的技术栈，比如：互联网，金融，电信……等等 ，大公司会有更多的技术投入，也需要更大规模的生产，所以，他们使用的技术通常来说都是比较工业化的。在技术选型上，千万不要被——“你看某个公司也在用这个技术”，或是一些在论 坛上看到的一些程序员吐槽技术的观点（没有任何的数据，只有自己的喜好）来决定自己的技术，还是看看主流大多数公司实际在用的技术栈，会更靠谱一些。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;选择全球流行的技术，而不是中国流行的技术&lt;/strong&gt;。技术这个东西一定是一个全球化的东西，不是一个局域化的事。所以，一定要选国际化的会更好。另外，千万不要被某些公司的“特别案例”骗过去了，那怕这个案例很性感，关键还是要看解决问题的思路和采用的技术是否具有普世性。只有普世性的技术有更强的生命力。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;尽可能的使用红利大的主流技术，而不要自己发明轮子，更不要魔改&lt;/strong&gt;。我见过好些个公司魔改开源软件，比如有个公司同魔改mesos，最后改着改着发现自己发明另一个 kubernetes。我还见过很多公司或技术团队喜欢自己发明自己的专用轮子，最后都会被主流开源软件所取代。完全没有必要。不重新发明轮子，不魔改，不是因为自己技术不能，而是因为，这个世界早已不是自己干所有事的年代了，这个时代是要想尽方法跟整个产业，整个技术社区融合和合作，这样才会有最大的收益。那些试图因为某个特例需要自成一套的玩法，短期没问题，但长期来说，我都不看好。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;绝大多数情况下，如无非常特殊要求，选 Java基本是不会错的&lt;/strong&gt;。一方面，这是因为 Java 的业务开发的生产力是非常好的，而且有 Spring 框架保障，代码很难写烂，另外，Java 的社区太成熟了，你需要的各种架构和技术都可以很容易获得，技术红利实在是太大。这种运行在JVM上的语言有太多太多的好处了。在 Java 的技术栈上，你的架构风险和架构的成本（无论是人力成本，时间成本和资金成本）从长期来说都是最优的&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;在我见过的公司中，好些公司的架构都被技术负责人个人的喜好、擅长和个人经验给绑架了，完全不是从一个客观的角度来进行技术选型。其实，从 0 到 1 的阶段，你用什么样的技术都行，如果你做一个简单的应用，没有事务处理没有复杂的交易流程，比如一些论坛、社交之类的应用，你用任何语言都行。但是如果有一天你的系统变复杂了，需要处理交易了，量也上来了，从 1 到 10，甚至从 10 到 100，你的开发团队也变大了，需要构建的系统越来越大，你可能会发现你只有一个选择，就是 Java。想想京东从.NET 到 Java，淘宝从 PHP 到 Java……&lt;/p&gt; &lt;p&gt;注，一些有主观喜好的人一定会对我上述对 Java 的描述感到不适，我还用一些证据说明一下——全中国所有的电商平台，几百家银行，三大电信运营商，所有的保险公司，劵商的系统，医院里的系统，电子政府系统，等等，基本都是用 Java 开发的，包括 AWS 的主流语言也是 Java，阿里云一开始用 C++/Python 写控制系统，后面也开始用 Java ……你可能会说 B站是用 go语言，但是你可能不知道 B 站的电商和大数据是用 Java……懂着数据分析的同学，建议上各大招聘网站上搜一下 Java 的职位数量，你就知道某个技术是否主流和热门……&lt;/p&gt; &lt;h4&gt;原则四：完备性会比性能更重要&lt;/h4&gt; &lt;p&gt;我发现好些公司的架构师做架构的时候，首要考虑的是架构的性能是否能够撑得住多大多大的流量，而不是考虑系统的完备性和扩展性。所以，我已经多次见过这样的案例了，一开始直接使用 MongoDB 这样的非关系型数据库，或是把数据直接放在 Redis 里，而直接放弃关系型数据库的数据完备性的模型，而在后来需要在数据上进行关系查询的时候，发现 NoSQL 的数据库在 Join 上都表现的太差，然后就开始各种飞线，为了不做 Join 就开始冗余数据，然而自己又维护不好冗余数据后带来的数据一致性的问题，导致数据上的各种错乱丢失。&lt;/p&gt; &lt;p&gt;所以，我给如下的一些如下的架构原则：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;使用最科学严谨的技术模型为主，并以不严谨的模型作为补充&lt;/strong&gt;。对于上面那个案例来说，就是——永远使用完备支持 ACID 的关系型数据库，然后用 NoSQL 作补充，而不是完全放弃关系型数据库。这里的原则就是所谓的“先紧后松”，一开始紧了，你可以慢慢松，但是开始松了，以后你想紧再也紧不过来了。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;性能上的东西，总是有很多解的&lt;/strong&gt;。我这么多年的经历告诉我，性能上的事，总是有解的，手段也是最多的，这个比起架构的完备性和扩展性来说真的不必太过担心。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;为了追求所谓的性能，把整个系统的完备性丢失掉，相当地得不偿失。&lt;/p&gt; &lt;h4&gt;原则五：制定并遵循服从标准、规范和最佳实践&lt;/h4&gt; &lt;p&gt;这个原则是非常重要的，因为只有服从了标准，你的架构才能够有更好的扩展性。比如：我经常性的见到很多公司的系统既没有服从业界标准，也没有形成自己公司的标准，感觉就像一群乌合之众一样。最典型的例子就是 HTTP 调用的状态返回码。业内给你的标准是 200表示成功，3xx 跳转，4xx 表示调用端出错，5xx 表示服务端出错，我实在是不明白为什么无论成功和失败大家都喜欢返回 200，然后在 body 里指出是否error（前两年我在微信公众号里看到一个有一定名气的互联网老兵推荐使用无论正确还是出错都返回 200 的做法，我在后台再三确认后，我发现这样的架构师真是害人不浅）。这样做最大的问题是——监控系统将在一种低效的状态下工作。监控系统需要把所有的网络请求包打开后才知道是否是错误，而且完全不知道是调用端出错还是服务端出错，于是一些像重试或熔断这样的控制系统完全不知道怎么搞（如果是 4xx错，那么重试或熔断是没有意义的，只有 5xx 才有意义）。&lt;strong&gt;有时候，我会有种越活越退步的感觉，错误码设计这种最基本最基础的东西为什么会没有？并且一个公司会任由着大家乱来？这些基础技能怎么就这样丢掉了？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;还有，我还见过一些公司，他们整个组织没有一个统一的用户 ID 的设计，各个系统之间同步用户的数据是通过用户的身份证 ID，是的，就是现实世界的身份证 ID，包括在网关上设置的用户白名单居然也是用身份证 ID。我对这个公司的内的用户隐私管理有很大的担忧。一个企业，一个组织，如果没有标准和规范，也就会有抽象，这一定是要出各种乱子的。&lt;/p&gt; &lt;p&gt;下面，我罗列一些你需要注意的标准和规范（包括但不限于）：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;服务 间调用的协议标准和规范&lt;/strong&gt;。这其中包括 Restful API路径, HTTP 方法、状态码、标准头、自定义头等，返回数据 JSon Scheme……等。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;一些命名的标准和规范&lt;/strong&gt;。这其中包括如：用户 ID，服务名、标签名、状态名、错误码、消息、数据库……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;日志和监控的规范&lt;/strong&gt;。这其中包括：日志格式，监控数据，采样要求，报警……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;配置上的规范&lt;/strong&gt;。这其中包括：操作系统配置、中间件配置，软件包……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;中间件使用的规范&lt;/strong&gt;。数据库，缓存、消息队列……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;软件和开发库版本统一&lt;/strong&gt;。整个组织架构内，软件或开发库的版本最好每年都升一次级，然后在各团队内统一。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这里重要说一下两个事：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Restful API 的规范&lt;/strong&gt;。我觉得是非常重要的，这里给两个我觉得写得最好的参考：&lt;a href="https://github.com/paypal/api-standards/blob/master/api-style-guide.md" target="_blank"&gt;Paypal&lt;/a&gt; 和 &lt;a href="https://github.com/microsoft/api-guidelines" target="_blank"&gt;Microsoft&lt;/a&gt; 。Restful API 有一个标准和规范最大的好处就是监视可以很容易地做各种统计分析，控制系统可以很容易的做流量编排和调度。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;另一个是服务调用链追踪&lt;/strong&gt;。对于服务调用链追踪来说，基本上都是参考于 &lt;a href="https://research.google/pubs/pub36356/" target="_blank"&gt;Google Dapper&lt;/a&gt; 这篇论文，目前有很多的实现，最严格的实现是 &lt;a href="https://zipkin.io/" target="_blank"&gt;Zipkin&lt;/a&gt;，这也是 Spring Cloud Sleuth 的底层实现。Zipkin 贴近 Google Dapper 论文的好处在于——无状态，快速地把 Span 发出来，不消耗服务应用侧的内存和 CPU。这意味着，监控系统宁可自己死了也不能干扰实际应用。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;软件升级&lt;/strong&gt;。我发现很多公司包括 BAT，他们完全没有软件升级的活动，全靠开发人员自发。然而，这种成体系的活动，是永远不可能靠大众的自发形成的。一个公司至少一年要有一次软件版本升级的review，然后形成软件版本的统一和一致，这样会极太简化系统架构的复杂度。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;原则六：重视架构扩展性和可运维性&lt;/h4&gt; &lt;p&gt;在我见过很多架构里，技术人员只考虑当下，但从来不考虑系统的未来扩展性和可运维性。所谓的管生不管养。如果你生下来的孩子胳膊少腿，严重畸形，那么未来是很难玩的。因为架构和软件不是写好就完的，是需要不断修改不断维护的，80%的软件成本都是在维护上。所以，如何让你的架构有更好的扩展性，可以更容易地运维，这个是比较重要的。所谓的扩展性，意味着，我可以很容易地加更多的功能，或是加入更多的系统，而所谓可运维，就是说我可以对线上的系统做任意的变更。扩展性要求的是有标准规范且不耦合的业务架构，可运维性要求的则是可控的能力，也就是一组各式各样的控制系统。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;通过服务编排架构来降低服务间的耦合&lt;/strong&gt;。比如：通过一个业务流程的专用服务，或是像 Workflow，Event Driven Architecture ， Broker，Gateway，Service Discovery 等这类的的中间件来降低服务间的依赖关系。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;通过服务发现或服务网关来降低服务依赖所带来的运维复杂度&lt;/strong&gt;。服务发现可以很好的降低相关依赖服务的运维复杂度，让你可以很轻松的上线或下线服务，或是进行服务伸缩。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;一定要使用各种软件设计的原则&lt;/strong&gt;。比如：像SOLID这样的原则（参看《&lt;a href="https://coolshell.cn/articles/4535.html" target="_blank"&gt;一些软件设计的原则&lt;/a&gt;》），IoC/DIP，SOA 或 Spring Cloud 等 架构的最佳实践（参看《&lt;a href="https://coolshell.cn/articles/5701.html" target="_blank"&gt;SteveY对Amazon和Google平台的吐槽&lt;/a&gt;》中的 Service Interface 的那几条军规），分布式系统架构的相关 实践（参看：《&lt;a href="https://coolshell.cn/articles/10910.html" target="_blank"&gt;分布式系统的事务处理&lt;/a&gt;》，或微软件的 《&lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/" target="_blank"&gt;Cloud Design Patterns&lt;/a&gt;》）……等等&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;原则七：对控制逻辑进行全面收口&lt;/h4&gt; &lt;p&gt;所有的程序都会有两种逻辑，一种是业务逻辑，一种是控制逻辑，业务逻辑就是完成业务的逻辑，控制逻辑是辅助，比如你用多线程，还是用分布式，是用数据库还是用文件，如何配置、部署，运维、监控，事务控制，服务发现，弹性伸缩，灰度发布，高并发，等等，等等 ……这些都是控制逻辑，跟业务逻辑没有一毛钱关系。控制逻辑的技术深度会通常会比业务逻辑要深一些，门槛也会要高一些，所以，最好要专业的程序员来负责控制逻辑的开发，统一规划统一管理，进行收口。这其中包括：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;流量收口&lt;/strong&gt;。包括南北向和东西向的流量的调度，主要通过流量网关，开发框架 SDK或 Service Mesh 这样的技术。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;服务治理收口&lt;/strong&gt;。包括：服务发现、健康检查，配置管理、事务、事件、重试、熔断、限流……主要通过开发框架 SDK – 如：Spring Cloud，或服务网格Service Mesh等技术。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;监控数据收口&lt;/strong&gt;。包括：日志、指标、调用链……主要通过一些标准主流的探针，再加上后台的数据清洗和数据存储来完成，最好是使用无侵入式的技术。监控的数据必须统一在一个地方进行关联，这样才会产生信息。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;资源调度有应用部署的收口&lt;/strong&gt;。包括：计算、网络和存储的收口，主要是通过容器化的方案，如k8s来完成。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;中间件的收口&lt;/strong&gt;。包括：数据库，消息，缓存，服务发现，网关……等等。这类的收口方式一般要在企业内部统一建立一个共享的云化的中间件资源池。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对此，这里的原则是：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;你要选择容易进行业务逻辑和控制逻辑分离的技术&lt;/strong&gt;。这里，Java 的 JVM+字节码注入+AOP 式的Spring 开发框架，会带给你太多的优势。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;你要选择可以享受“前人种树，后人乘凉”的有技术红利的技术&lt;/strong&gt;。如：有庞大社区而且相互兼容的技术，如：Java, Docker, Ansible，HTTP，Telegraf/Collectd……&lt;/li&gt; &lt;li&gt;&lt;strong&gt;中间件你要使用可以 支持HA集群和多租户的技术&lt;/strong&gt;。这里基本上所有的主流中间件都会支持 HA 集群方式的。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;原则八：不要迁就老旧系统的技术债务&lt;/h4&gt; &lt;p&gt;我发现很多公司都很非常大的技术债务，这些债务具体表现如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;使用老旧的技术&lt;/strong&gt;。比如，使用HTTP1.0， Java 1.6，Websphere，ESB，基于 socket的通讯协议，过时的模型……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;不合理的设计&lt;/strong&gt;。比如，在 gateway 中写大量的业务逻辑，单体架构，数据和业务逻辑深度耦合，错误的系统架构（把缓存当数据库，用消息队列同步数据）……等等&lt;/li&gt; &lt;li&gt;&lt;strong&gt;缺少配套设施&lt;/strong&gt;。比如，没有自动化测试，没有好的软件文档，没有质量好的代码，没有标准和规范……等等&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;来找我寻求技术帮助的人都有各种各样的问题。我都会对他们苦口婆心地说同样的一句话——“&lt;strong&gt;如果你是来找我 case-by-case 解决问题，我兴趣不大，因为，你们千万不要寄希望能够很简单的把一辆夏利车改成一辆法拉利跑车，或是把一栋地基没打好的歪楼搞正。以前欠下的技术债，都得要还，没打好的地基要重新打，没建配套设施都要建。这些基础设施如果不按照正确科学的方式建立的话，你是不可能有一个好的的系统，我也没办法帮你 case-by-case 的解决问题……&lt;/strong&gt;”，一开始，他们都会对我说，没问题，我们就是要还债，但是，最后发现要还的债真多，有点承受不了，就开始现原形了。&lt;/p&gt; &lt;p&gt;他们开始为自己的“欠的技术债”找各种合理化的理由——给你解释各种各样的历史原因和不得以而为之的理由。谈着谈着，让我有一种感觉——他们希望得到一种什么都不改什么都不付出的方式就可以进步的心态，他们宁可让新的技术 low 下来迁就于这些技术债，把新的技术滥用地乱七八糟的。有一个公司，他们的系统架构和技术选型基本都搞错了，使用错误的模型构建系统，导致整个系统的性能非常之差，也才几千万条数据，但他们想的不是还债，不是把地基和配套设施建好，而且要把楼修的更高，上更多的系统——他们觉得现有的系统挺好，性能问题的原因是他们没一个大数据平台，所以要建大数据平台……&lt;/p&gt; &lt;p&gt;我见过很多很多公司，包括大如 BAT 这样的公司，都会在原来的技术债上进行更多的建设，然后，技术债越来越大，利息越来越大，最终成为一个高利贷，再也还不了（我在《&lt;a href="https://coolshell.cn/articles/11656.html" target="_blank"&gt;开发团队的效率&lt;/a&gt;》一文中讲过一个 WatchDog 的架构模式，一个系统烂了，不是去改这个系统，而是在旁边建一个系统来看着它，我很难理解为什么会有这样的逻辑，也许是为了要解决更多的就业……）&lt;/p&gt; &lt;p&gt;这里有几个原则和方法我是非常坚持的，分享给大家：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;与其花大力气迁就技术债务，不如直接还技术债。是所谓的长痛不如短痛。&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;建设没有技术债的“新城区”，并通过“&lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer" target="_blank"&gt;防腐层&lt;/a&gt; ”的架构模型，不要让技术债侵入“新城区”&lt;/strong&gt;。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;原则九：不要依赖自己的经验，要依赖于数据和学习&lt;/h4&gt; &lt;p&gt;有好些人来找我跟我说他们的技术问题，然后希望我能够给他们一个答案。我说，我需要了解一下你现有系统的情况，也就是需要先做个诊断，我只有得到这些数据后，我才可能明白真正的原因是什么 ，我才可能给你做出一个比较好的技术方案。我个人觉得这是一种对对方负责的方法，因为技术手段太多了，所有的技术手段都有适应的场景，并且有各种 trade-off，所以，只有调研完后才能做出决定。这跟医生看病是一样的，确诊病因不能靠经验，还是要靠诊断数据。在科学面前，所有的经验都是靠不住的……&lt;/p&gt; &lt;p&gt;另外，如果有一天你在做技术决定的时候，开始 凭自己以往的经验，那么你就已经不可能再成长了。人都是不可能通过不断重复过去而进步的，人的进步从来都是通过学习自己不知道的东西。所以，千万不要依赖于自己的经验做决定。做任何决定之前，最好花上一点时间，上网查一下相关的资料，技术博客，文章，论文等 ，同时，也看看各个公司，或是各个开源软件他们是怎么做的？然后，比较多种方案的 Pros/Cons，最终形成自己的决定，这样，才可能做出一个更好的决定。&lt;/p&gt; &lt;h4&gt;原则十：千万要小心 X – Y 问题，要追问原始需求&lt;/h4&gt; &lt;p&gt;对于 &lt;a href="https://coolshell.cn/articles/10804.html" target="_blank"&gt;X-Y 问题&lt;/a&gt;，也就是说，用户为了解决 X问题，他觉得用 Y 可以解，于是问我 Y 怎么搞，结果搞到最后，发现原来要解决的 X 问题，这个时候最好的解决方案不是 Y，而是 Z。 这种 X-Y 问题真是相当之多，见的太多太多了。所以，每次用户来找我的时候，我都要不断地追问什么是 X 问题。&lt;/p&gt; &lt;p&gt;比如，好些用户都会来问我他们要一个大数据流式处理，结果追问具体要解决什么样的问题时，才发现他们的问题是因为服务中有大量的状态，需要把相同用户的数据请求放在同一个服务上处理，而且设计上导致一个慢函数拖慢整个应用服务。最终就是做一下性能调优就好了，根本没有必要上什么大数据的流式处理。&lt;/p&gt; &lt;p&gt;我很喜欢追问为什么 ，这种追问，会让客户也跟着来一起重新思考。比如，有个客户来找我评估的一个技术架构的决定，从理论上来说，好像这个架构在用户的这个场景下非常不错。但是，这个场景和这个架构是我职业生涯从来没有见过的。于是，我开始追问这个为什么会是这么一个场景？当我追问的时候，我发现用户都感到这个场景的各种不合理。最后引起了大家非常深刻的研讨，最终用户把那个场景修正后，而架构就突然就变成了一个常见且成熟的的模型……&lt;/p&gt; &lt;h4&gt;原则十一：激进胜于保守，创新与实用并不冲突&lt;/h4&gt; &lt;p&gt;我对技术的态度是比较激进的，但是，所谓的激进并不是瞎搞，也不是见新技术就上，而是积极拥抱会改变未来的新技术，如：Docker/Go，我就非常快地跟进，但是像区块链或是 Rust 这样的，我就不是很积极。因为，其并没有命中我认为的技术趋势的几个特征（参看《&lt;a href="https://coolshell.cn/articles/18190.html" target="_blank"&gt;Go,Docker 和新技术&lt;/a&gt; 》）。当然，我也不是不喜欢的就不学了，我对区块链和 Rust 我一样学习，我也知道这些技术的优势，但我不会大规模使用它们。另外，我也尊重保守的决定，这里面没有对和错。但是，我个人觉得对技术激进的态度比起保守来说有太多的好处了。一方面来说，对于用户来说，很大程度上来说，新技术通常都表面有很好的竞争力，而且我见太多这样成功的公司都在积极拥抱新的技术的，而保守的通常来说都越来越不好。&lt;/p&gt; &lt;p&gt;有一些人会跟我说，我们是实用主义，我们不需要创新，能解决当下的问题就好，所以，我们不需要新技术，现有的技术用好就行了。这类的公司，他们的技术设计第一天就在负债，虽然可以解决当下问题，但是马上就会出现新的问题，然后他们会疲于解决各种问题。最后呢，最后还是会走到新的技术上。&lt;/p&gt; &lt;p&gt;这里的逻辑很简单 —— &lt;strong&gt;进步永远来自于探索，探索是要付出代价的，但是收益更大&lt;/strong&gt;。对我而言，不敢冒险才是最大的冒险，不敢犯错才是最大的错误，害怕失去会让你失去的更多……&lt;/p&gt; &lt;p&gt;（全文完）&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;文章转载于 &lt;a href="https://coolshell.cn/articles/21672.html" target="_blank"&gt;酷 壳 – CoolShell&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sun, 26 Dec 2021 14:49:00 GMT</pubDate>
    </item>
    <item>
      <title>屏蔽国内IP访问网站</title>
      <link>https://maruifu.cn/article/221</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;今天有个朋友给我说让我把网站弄一下，别让国内的人访问，主要是为了保证营销的数据准确性和防止同行抄袭。当然是没办法彻底屏蔽的，防小人不防君子吧。我用的是Nginx，&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;首先我们要去弄到国内的IP地址段，访问网站 http://www.ip2location.com/free/visitor-blocker ，点击左侧的“Firewall List by Country”选项卡。点击下载ip文件&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/25/image-20211225233331215.png" alt="image-20211225233331215" title="image-20211225233331215" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;顺便要把你现在的IP最好不要加进去，否则你自己会访问不了。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;下载不了的可以指定 使用&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/file/firewall.txt" target="_blank"&gt;国内IP地址列表&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;方法一&lt;/h2&gt; &lt;p&gt;复制整段代码到你的Nginx配置文件里面即可。&lt;/p&gt; &lt;h2&gt;方法二&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;把文件去掉第一行的“location / {”和最后一行的“}”，重命名为&lt;code&gt;blockip.conf&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在nginx配置的http块下面加上&lt;code&gt;include blockip.conf;&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可以加载个自定义页面&lt;/p&gt; &lt;p&gt;在&lt;code&gt;/www/wwwroot/watch&lt;/code&gt;目录下新建一个 &lt;code&gt;403.html&lt;/code&gt;文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;error_page 403 /403.html; location = /403.html{  root  /www/wwwroot/watch;  allow all; } &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Sat, 25 Dec 2021 15:47:51 GMT</pubDate>
    </item>
    <item>
      <title>一份超详细的MySQL高性能优化实战总结！</title>
      <link>https://maruifu.cn/article/220</link>
      <content:encoded>&lt;p&gt;MySQL 对于很多 Linux 从业者而言，是一个非常棘手的问题，多数情况都是因为对数据库出现问题的情况和处理思路不清晰。&lt;/p&gt; &lt;p&gt;在进行 MySQL 的优化之前必须要了解的就是 MySQL 的查询过程，很多的查询优化工作实际上就是遵循一些原则让 MySQL 的优化器能够按照预想的合理方式运行而已。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/f9c4a2b74a824b02afdd4d26da937501.jpg" alt="一份超详细的MySQL高性能优化实战总结！" title="一份超详细的MySQL高性能优化实战总结！" /&gt;&lt;/p&gt; &lt;h1&gt;优化的哲学&lt;/h1&gt; &lt;p&gt;注：优化有风险，修改需谨慎。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;优化可能带来的问题：&lt;/li&gt; &lt;li&gt;优化不总是对一个单纯的环境进行，还很可能是一个复杂的已投产的系统。&lt;/li&gt; &lt;li&gt;优化手段本来就有很大的风险，只不过你没能力意识到和预见到。&lt;/li&gt; &lt;li&gt;任何的技术可以解决一个问题，但必然存在带来一个问题的风险。&lt;/li&gt; &lt;li&gt;对于优化来说解决问题而带来的问题，控制在可接受的范围内才是有成果。&lt;/li&gt; &lt;li&gt;保持现状或出现更差的情况都是失败。&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;优化的需求&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;稳定性和业务可持续性，通常比性能更重要。&lt;/li&gt; &lt;li&gt;优化不可避免涉及到变更，变更就有风险。&lt;/li&gt; &lt;li&gt;优化使性能变好，维持和变差是等概率事件。&lt;/li&gt; &lt;li&gt;切记优化，应该是各部门协同，共同参与的工作，任何单一部门都不能对数据库进行优化。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;所以优化工作，是由业务需求驱使的!&lt;/p&gt; &lt;p&gt;优化由谁参与?在进行数据库优化时，应由数据库管理员、业务部门代表、应用程序架构师、应用程序设计人员、应用程序开发人员、硬件及系统管理员、存储管理员等，业务相关人员共同参与。&lt;/p&gt; &lt;h1&gt;优化思路&lt;/h1&gt; &lt;h2&gt;优化什么&lt;/h2&gt; &lt;p&gt;在数据库优化上有两个主要方面：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;安全：数据可持续性。&lt;/li&gt; &lt;li&gt;性能：数据的高性能访问。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;优化范围&lt;/h2&gt; &lt;p&gt;存储、主机和操作系统方面：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;主机架构稳定性&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;I/O 规划及配置&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Swap 交换分区&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;OS 内核参数和网络问题&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;应用程序方面：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;应用程序稳定性&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;SQL 语句性能&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;串行访问资源&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;性能欠佳会话管理&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;这个应用适不适合用 MySQL&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;数据库优化方面：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;内存&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;数据库结构(物理&amp;amp;逻辑)&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;实例配置&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;说明：不管是设计系统、定位问题还是优化，都可以按照这个顺序执行。&lt;/p&gt; &lt;h2&gt;优化维度&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/3cf4c8205d054deba5d2baee060ad793.jpg" alt="一份超详细的MySQL高性能优化实战总结！" title="一份超详细的MySQL高性能优化实战总结！" /&gt;&lt;/p&gt; &lt;p&gt;数据库优化维度有如下四个：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;硬件&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;系统配置&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;数据库表结构&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;&lt;strong&gt;SQL 及索引&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;优化选择&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;优化成本：硬件&amp;gt;系统配置&amp;gt;数据库表结构&amp;gt;SQL 及索引。&lt;/li&gt; &lt;li&gt;优化效果：硬件&amp;lt;系统配置&amp;lt;数据库表结构&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;优化工具&lt;/h1&gt; &lt;p&gt;检查问题常用的 12 个工具：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;MySQL&lt;/li&gt; &lt;li&gt;mysqladmin：MySQL 客户端，可进行管理操作&lt;/li&gt; &lt;li&gt;mysqlshow：功能强大的查看 shell 命令&lt;/li&gt; &lt;li&gt;SHOW [SESSION | GLOBAL] variables：查看数据库参数信息&lt;/li&gt; &lt;li&gt;SHOW [SESSION | GLOBAL] STATUS：查看数据库的状态信息&lt;/li&gt; &lt;li&gt;information_schema：获取元数据的方法&lt;/li&gt; &lt;li&gt;SHOW ENGINE INNODB STATUS：Innodb 引擎的所有状态&lt;/li&gt; &lt;li&gt;SHOW PROCESSLIST：查看当前所有连接的 session 状态&lt;/li&gt; &lt;li&gt;explain：获取查询语句的执行计划&lt;/li&gt; &lt;li&gt;show index：查看表的索引信息&lt;/li&gt; &lt;li&gt;slow-log：记录慢查询语句&lt;/li&gt; &lt;li&gt;mysqldumpslow：分析 slowlog 文件的工具&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;不常用但好用的 7 个工具：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Zabbix：监控主机、系统、数据库(部署 Zabbix 监控平台)&lt;/li&gt; &lt;li&gt;pt-query-digest：分析慢日志&lt;/li&gt; &lt;li&gt;MySQL slap：分析慢日志&lt;/li&gt; &lt;li&gt;sysbench：压力测试工具&lt;/li&gt; &lt;li&gt;MySQL profiling：统计数据库整体状态工具&lt;/li&gt; &lt;li&gt;Performance Schema：MySQL 性能状态统计的数据&lt;/li&gt; &lt;li&gt;workbench：管理、备份、监控、分析、优化工具(比较费资源)&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;数据库层面问题&lt;/h1&gt; &lt;h2&gt;解决思路&lt;/h2&gt; &lt;p&gt;一般应急调优的思路：针对突然的业务办理卡顿，无法进行正常的业务处理，需要马上解决的场景。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#查看正在执行的sql语句和进程 show processlist  #通过执行计划判断，索引问题（有没有、合不合理）或者语句本身问题  explain select id ,name from stu where name='clsn'; # ALL id name age sex   select id,name from stu where id=2-1 函数 结果集&amp;gt;30;  　 show index from table;   # 查询锁状态  show status like '%lock%'; # 查询哪些表锁了 show OPEN TABLES where In_use &amp;gt; 0; # 查看造成死锁的sql语句 innodb引擎的运行时信息 show engine innodb status; # 杀掉有问题的session  kill SESSION_ID;   # 查看正在执行的事务 select * from information_schema.INNODB_TRX; # 查看正在锁的事物 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS # 查看等待锁的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;MySQL锁定状态查看命令&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;Status&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;Checking table&lt;/td&gt;&lt;td&gt;正在检查数据表（这是自动的）。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Closing tables&lt;/td&gt;&lt;td&gt;正在将表中修改的数据刷新到磁盘中，同时正在关闭已经用完的表。这是一个很快的操作，如果不是这样的话，就应该确认磁盘空间是否已经满了或者磁盘是否正处于重负中。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Connect Out&lt;/td&gt;&lt;td&gt;复制从服务器正在连接主服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Copying to tmp table on disk&lt;/td&gt;&lt;td&gt;由于临时结果集大于tmp_table_size，正在将临时表从内存存储转为磁盘存储以此节省内存。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Creating tmp table&lt;/td&gt;&lt;td&gt;正在创建临时表以存放部分查询结果。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;deleting from main table&lt;/td&gt;&lt;td&gt;服务器正在执行多表删除中的第一部分，刚删除第一个表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;deleting from reference tables&lt;/td&gt;&lt;td&gt;服务器正在执行多表删除中的第二部分，正在删除其他表的记录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Flushing tables&lt;/td&gt;&lt;td&gt;正在执行FLUSH TABLES，等待其他线程关闭数据表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Killed&lt;/td&gt;&lt;td&gt;发送了一个kill请求给某线程，那么这个线程将会检查kill标志位，同时会放弃下一个kill请求。MySQL会在每次的主循环中检查kill标志位，不过有些情况下该线程可能会过一小段才能死掉。如果该线程程被其他线程锁住了，那么kill请求会在锁释放时马上生效。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Locked&lt;/td&gt;&lt;td&gt;被其他查询锁住了。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Sending data&lt;/td&gt;&lt;td&gt;正在处理SELECT查询的记录，同时正在把结果发送给客户端。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Sorting for group&lt;/td&gt;&lt;td&gt;正在为GROUP BY做排序。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Sorting for order&lt;/td&gt;&lt;td&gt;正在为ORDER BY做排序。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Opening tables&lt;/td&gt;&lt;td&gt;这个过程应该会很快，除非受到其他因素的干扰。例如，在执ALTER TABLE或LOCK TABLE语句行完以前，数据表无法被其他线程打开。正尝试打开一个表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Removing duplicates&lt;/td&gt;&lt;td&gt;正在执行一个SELECT DISTINCT方式的查询，但是MySQL无法在前一个阶段优化掉那些重复的记录。因此，MySQL需要再次去掉重复的记录，然后再把结果发送给客户端。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Reopen table&lt;/td&gt;&lt;td&gt;获得了对一个表的锁，但是必须在表结构修改之后才能获得这个锁。已经释放锁，关闭数据表，正尝试重新打开数据表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Repair by sorting&lt;/td&gt;&lt;td&gt;修复指令正在排序以创建索引。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Repair with keycache&lt;/td&gt;&lt;td&gt;修复指令正在利用索引缓存一个一个地创建新索引。它会比Repair by sorting慢些。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Searching rows for update&lt;/td&gt;&lt;td&gt;正在讲符合条件的记录找出来以备更新。它必须在UPDATE要修改相关的记录之前就完成了。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Sleeping&lt;/td&gt;&lt;td&gt;正在等待客户端发送新请求。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;System lock&lt;/td&gt;&lt;td&gt;正在等待取得一个外部的系统锁。如果当前没有运行多个mysqld服务器同时请求同一个表，那么可以通过增加--skip-external-locking参数来禁止外部系统锁。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Upgrading lock&lt;/td&gt;&lt;td&gt;INSERT DELAYED正在尝试取得一个锁表以插入新记录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Updating&lt;/td&gt;&lt;td&gt;正在搜索匹配的记录，并且修改它们。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;User Lock&lt;/td&gt;&lt;td&gt;正在等待GET_LOCK()。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Waiting for tables&lt;/td&gt;&lt;td&gt;该线程得到通知，数据表结构已经被修改了，需要重新打开数据表以取得新的结构。然后，为了能的重新打开数据表，必须等到所有其他线程关闭这个表。以下几种情况下会产生这个通知：FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE,或OPTIMIZE TABLE。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;waiting for handler insert&lt;/td&gt;&lt;td&gt;INSERT DELAYED已经处理完了所有待处理的插入操作，正在等待新的请求。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;常规调优思路&lt;/h2&gt; &lt;p&gt;针对业务周期性的卡顿，例如在每天 10-11 点业务特别慢，但是还能够使用，过了这段时间就好了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;查看slowlog，分析slowlog，分析出查询慢的语句；  按照一定优先级，一个一个排查所有慢语句；  分析top SQL，进行explain调试，查看语句执行时间；  调整索引或语句本身。  &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;系统层面问题&lt;/h1&gt; &lt;h2&gt;解决思路&lt;/h2&gt; &lt;p&gt;CPU方面：vmstat、sar top、htop、nmon、mpstat。&lt;/p&gt; &lt;p&gt;内存：free、ps-aux。&lt;/p&gt; &lt;p&gt;IO 设备(磁盘、网络)：iostat、ss、netstat、iptraf、iftop、lsof。&lt;/p&gt; &lt;p&gt;vmstat 命令说明：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Procs：r 显示有多少进程正在等待 CPU 时间。b 显示处于不可中断的休眠的进程数量。在等待 I/O。&lt;/li&gt; &lt;li&gt;Memory：swpd 显示被交换到磁盘的数据块的数量。未被使用的数据块，用户缓冲数据块，用于操作系统的数据块的数量。&lt;/li&gt; &lt;li&gt;Swap：操作系统每秒从磁盘上交换到内存和从内存交换到磁盘的数据块的数量。s1 和 s0 最好是 0。&lt;/li&gt; &lt;li&gt;IO：每秒从设备中读入 b1 的写入到设备 b0 的数据块的数量。反映了磁盘 I/O。&lt;/li&gt; &lt;li&gt;System：显示了每秒发生中断的数量(in)和上下文交换(cs)的数量。&lt;/li&gt; &lt;li&gt;CPU：显示用于运行用户代码，系统代码，空闲，等待 I/O 的 CPU 时间。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;iostat 命令说明：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;实例命令：iostat -dk 1 5;iostat -d -k -x 5 (查看设备使用率(%util)和响应时间(await))。&lt;/li&gt; &lt;li&gt;TPS：该设备每秒的传输次数。“一次传输”意思是“一次 I/O 请求”。多个逻辑请求可能会被合并为“一次 I/O 请求”。&lt;/li&gt; &lt;li&gt;iops ：硬件出厂的时候，厂家定义的一个每秒最大的 IO 次数。&lt;/li&gt; &lt;li&gt;&amp;quot;一次传输&amp;quot;请求的大小是未知的。&lt;/li&gt; &lt;li&gt;KB_read/s：每秒从设备(drive expressed)读取的数据量。&lt;/li&gt; &lt;li&gt;KB_wrtn/s：每秒向设备(drive expressed)写入的数据量。&lt;/li&gt; &lt;li&gt;KB_read：读取的总数据量。&lt;/li&gt; &lt;li&gt;KB_wrtn：写入的总数量数据量;这些单位都为 Kilobytes。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;解决办法&lt;/h2&gt; &lt;p&gt;你认为到底负载高好，还是低好呢?在实际的生产中，一般认为 CPU 只要不超过 90% 都没什么问题。当然不排除下面这些特殊情况。&lt;/p&gt; &lt;p&gt;CPU 负载高，IO 负载低：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;内存不够&lt;/li&gt; &lt;li&gt;磁盘性能差&lt;/li&gt; &lt;li&gt;SQL 问题：去数据库层，进一步排查 SQL 问题&lt;/li&gt; &lt;li&gt;IO 出问题了(磁盘到临界了、raid 设计不好、raid 降级、锁、在单位时间内 TPS 过高)&lt;/li&gt; &lt;li&gt;TPS 过高：大量的小数据 IO、大量的全表扫描&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;IO 负载高，CPU 负载低：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;大量小的 IO 写操作&lt;/li&gt; &lt;li&gt;autocommit，产生大量小 IO;IO/PS，磁盘的一个定值，硬件出厂的时候，厂家定义的一个每秒最大的 IO 次数。&lt;/li&gt; &lt;li&gt;大量大的 IO 写操作：SQL 问题的几率比较大&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;IO和 CPU 负载都很高：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;硬件不够了或 SQL 存在问题&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;基础优化&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;优化思路:&lt;/p&gt; &lt;p&gt;定位问题点吮吸：硬件&amp;gt;系统&amp;gt;应用&amp;gt;数据库&amp;gt;架构(高可用、读写分离、分库分表)。&lt;/p&gt; &lt;p&gt;处理方向：明确优化目标、性能和安全的折中、防患未然。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;&lt;strong&gt;硬件优化&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;①主机方面&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;根据数据库类型，主机 CPU 选择、内存容量选择、磁盘选择：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;平衡内存和磁盘资源&lt;/li&gt; &lt;li&gt;随机的 I/O 和顺序的 I/O&lt;/li&gt; &lt;li&gt;主机 RAID 卡的 BBU(Battery Backup Unit)关闭&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;②CPU 的选择&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;CPU 的两个关键因素：核数、主频。根据不同的业务类型进行选择：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;CPU 密集型：计算比较多，OLTP 主频很高的 CPU、核数还要多。&lt;/li&gt; &lt;li&gt;IO 密集型：查询比较，OLAP 核数要多，主频不一定高的。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;③内存的选择&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;OLAP 类型数据库，需要更多内存，和数据获取量级有关。OLTP 类型数据一般内存是 CPU 核心数量的 2 倍到 4 倍，没有最佳实践。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;④存储方面&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;根据存储数据种类的不同，选择不同的存储设备，配置合理的 RAID 级别(raid5、raid10、热备盘)。&lt;/p&gt; &lt;p&gt;对于操作系统来讲，不需要太特殊的选择，最好做好冗余(raid1)(ssd、sas、sata)。&lt;/p&gt; &lt;p&gt;主机 raid 卡选择：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;实现操作系统磁盘的冗余(raid1)&lt;/li&gt; &lt;li&gt;平衡内存和磁盘资源&lt;/li&gt; &lt;li&gt;随机的 I/O 和顺序的 I/O&lt;/li&gt; &lt;li&gt;主机 raid 卡的 BBU(Battery Backup Unit)要关闭&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;⑤网络设备方面&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;使用流量支持更高的网络设备(交换机、路由器、网线、网卡、HBA 卡)。注意：以上这些规划应该在初始设计系统时就应该考虑好。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;服务器硬件优化关键点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;物理状态灯&lt;/li&gt; &lt;li&gt;自带管理设备：远程控制卡(FENCE设备：ipmi ilo idarc)、开关机、硬件监控。&lt;/li&gt; &lt;li&gt;第三方的监控软件、设备(snmp、agent)对物理设施进行监控。&lt;/li&gt; &lt;li&gt;存储设备：自带的监控平台。EMC2(HP 收购了)、 日立(HDS)、IBM 低端 OEM HDS、高端存储是自己技术，华为存储。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h2&gt;系统优化&lt;/h2&gt; &lt;p&gt;CPU：基本不需要调整，在硬件选择方面下功夫即可。&lt;/p&gt; &lt;p&gt;内存：基本不需要调整，在硬件选择方面下功夫即可。&lt;/p&gt; &lt;p&gt;SWAP：MySQL 尽量避免使用 Swap。阿里云的服务器中默认 swap 为 0。&lt;/p&gt; &lt;p&gt;IO ：raid、no lvm、ext4 或 xfs、ssd、IO 调度策略。&lt;/p&gt; &lt;p&gt;Swap 调整(不使用 swap 分区)：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/proc/sys/vm/swappiness的内容改成0(临时)，/etc/sysctl. conf上添加vm.swappiness=0(永久)  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个参数决定了 Linux 是倾向于使用 Swap，还是倾向于释放文件系统 Cache。在内存紧张的情况下，数值越低越倾向于释放文件系统 Cache。&lt;/p&gt; &lt;p&gt;当然，这个参数只能减少使用 Swap 的概率，并不能避免 Linux 使用 Swap。&lt;/p&gt; &lt;p&gt;修改 MySQL 的配置参数 innodb_flush_ method，开启 O_DIRECT 模式。&lt;/p&gt; &lt;p&gt;这种情况下，InnoDB 的 buffer pool 会直接绕过文件系统 Cache 来访问磁盘，但是 redo log 依旧会使用文件系统 Cache。&lt;/p&gt; &lt;p&gt;值得注意的是，Redo log 是覆写模式的，即使使用了文件系统的 Cache，也不会占用太多。&lt;/p&gt; &lt;p&gt;IO 调度策略：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#echo deadline&amp;gt;/sys/block/sda/queue/scheduler 临时修改为deadline  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;永久修改：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vi /boot/grub/grub.conf  更改到如下内容:  kernel /boot/vmlinuz-2.6.18-8.el5 ro root=LABEL=/ elevator=deadline rhgb quiet   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;系统参数调整&lt;/h3&gt; &lt;p&gt;Linux 系统内核参数优化：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim/etc/sysctl.conf  net.ipv4.ip_local_port_range = 1024 65535：# 用户端口范围  net.ipv4.tcp_max_syn_backlog = 4096  net.ipv4.tcp_fin_timeout = 30  fs.file-max=65535：# 系统最大文件句柄，控制的是能打开文件最大数量  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;用户限制参数(MySQL 可以不设置以下配置)：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim/etc/security/limits.conf  * soft nproc 65535  * hard nproc 65535  * soft nofile 65535  * hard nofile 65535  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;应用优化&lt;/h2&gt; &lt;p&gt;业务应用和数据库应用独立。&lt;/p&gt; &lt;p&gt;防火墙：iptables、selinux 等其他无用服务(关闭)：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;chkconfig --level 23456 acpid off   chkconfig --level 23456 anacron off   chkconfig --level 23456 autofs off   chkconfig --level 23456 avahi-daemon off   chkconfig --level 23456 bluetooth off   chkconfig --level 23456 cups off   chkconfig --level 23456 firstboot off   chkconfig --level 23456 haldaemon off   chkconfig --level 23456 hplip off   chkconfig --level 23456 ip6tables off   chkconfig --level 23456 iptables off   chkconfig --level 23456 isdn off   chkconfig --level 23456 pcscd off   chkconfig --level 23456 sendmail off   chkconfig --level 23456 yum-updatesd off  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装图形界面的服务器不要启动图形界面 runlevel 3。&lt;/p&gt; &lt;p&gt;另外，思考将来我们的业务是否真的需要 MySQL，还是使用其他种类的数据库。用数据库的最高境界就是不用数据库。&lt;/p&gt; &lt;h2&gt;数据库优化&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;SQL 优化方向：执行计划，索引，SQL 改写&lt;/p&gt; &lt;p&gt;架构优化方向：高可用架构，高性能架构，分库分表&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;数据库参数优化&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;①调整&lt;/strong&gt;实例整体(高级优化，扩展)：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;thread_concurrency：# 并发线程数量个数  sort_buffer_size：# 排序缓存  read_buffer_size：# 顺序读取缓存  read_rnd_buffer_size：# 随机读取缓存  key_buffer_size：# 索引缓存  thread_cache_size：# (1G—&amp;gt;8, 2G—&amp;gt;16, 3G—&amp;gt;32, &amp;gt;3G—&amp;gt;64)  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;**②连接层(基础优化)**设置合理的连接客户和连接方式：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;max_connections # 最大连接数，看交易笔数设置  max_connect_errors # 最大错误连接数，能大则大  connect_timeout # 连接超时  max_user_connections # 最大用户连接数  skip-name-resolve # 跳过域名解析  wait_timeout # 等待超时  back_log # 可以在堆栈中的连接数量  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;③SQL 层(基础优化)&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;query_cache_size： 查询缓存 &amp;gt;&amp;gt;&amp;gt; OLAP 类型数据库，需要重点加大此内存缓存，但是一般不会超过 GB。&lt;/p&gt; &lt;p&gt;对于经常被修改的数据，缓存会马上失效。我们可以使用内存数据库(redis、memecache)，替代它的功能。&lt;/p&gt; &lt;h2&gt;存储引擎层优化&lt;/h2&gt; &lt;p&gt;innodb 基础优化参数：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;default-storage-engine  innodb_buffer_pool_size # 没有固定大小，50%测试值，看看情况再微调。但是尽量设置不要超过物理内存70%  innodb_file_per_table=(1,0)  innodb_flush_log_at_trx_commit=(0,1,2) # 1是最安全的，0是性能最高，2折中  binlog_sync  Innodb_flush_method=(O_DIRECT, fdatasync)  innodb_log_buffer_size # 100M以下  innodb_log_file_size # 100M 以下  innodb_log_files_in_group # 5个成员以下,一般2-3个够用（iblogfile0-N）  innodb_max_dirty_pages_pct # 达到百分之75的时候刷写 内存脏页到磁盘。  log_bin  max_binlog_cache_size # 可以不设置  max_binlog_size # 可以不设置  innodb_additional_mem_pool_size #小于2G内存的机器，推荐值是20M。32G内存以上100M  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 19 Dec 2021 16:09:00 GMT</pubDate>
    </item>
    <item>
      <title>一个简单案例，带你看懂GC日志！</title>
      <link>https://maruifu.cn/article/219</link>
      <content:encoded>&lt;h2&gt;代码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;public class Main {     public static void main(String[] args) {     byte[] array1 = new byte[4 * 1024 * 1024];     array1 = null;       byte[] array2 = new byte[2 * 1024 * 1024];     byte[] array3 = new byte[2 * 1024 * 1024];     byte[] array4 = new byte[2 * 1024 * 1024];     byte[] array5 = new byte[128 * 1024];     byte[] array6 = new byte[2 * 1024 * 1024];   } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;参数&lt;/h2&gt; &lt;h3&gt;参数设置运行&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;-XX:NewSize=10M -XX:MaxNewSize=10M -XX:InitialHeapSize=20M -XX:MaxHeapSize=20M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;参数介绍&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;-XX:NewSize:初始年轻代大小 -XX:MaxNewSize:最大年轻代大小 -XX:InitialHeapSize:定义堆的初始化大小，默认值是物理内存的1/64，其实就是:-Xms -XX:MaxHeapSize:定义最大堆的大小，默认为物理内存的1/4，其实就是:-Xmx -XX:SurvivorRatio:Eden区与Survivor区的大小比值 -XX:MaxTenuringThreshold:年轻代对象转换为老年代对象最大年龄值 -XX:PretenureSizeThreshold=3M:对象大小超过3M时直接在老年代分配内存 -XX:+UseParNewGC:使用ParNew收集器 -XX:+UseConcMarkSweepGC:使用CMS收集器 -XX:+PrintGCDetails:GC时打印详细信息 -Xloggc:输出GC日志信息到文件中 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;日志&lt;/h2&gt; &lt;h3&gt;日志情况&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;0.095: [GC (Allocation Failure) 0.095: [ParNew (promotion failed): 7838K-&amp;gt;8391K(9216K), 0.0029438 secs]0.098: [CMS: 8194K-&amp;gt;6659K(10240K), 0.0024311 secs] 11934K-&amp;gt;6659K(19456K), [Metaspace: 3121K-&amp;gt;3121K(1056768K)], 0.0055393 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]  Heap  par new generation   total 9216K, used 2214K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)   eden space 8192K,  27% used [0x00000007bec00000, 0x00000007bee29820, 0x00000007bf400000)   from space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)   to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)  concurrent mark-sweep generation total 10240K, used 6659K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)  Metaspace       used 3142K, capacity 4496K, committed 4864K, reserved 1056768K   class space    used 347K, capacity 388K, committed 512K, reserved 1048576K  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;日志详解&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;GC：表明进行了一次垃圾回收，前面没有Full修饰，表明这是一次Young GC  Allocation Failure：表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了  ParNew：表明本次GC发生在年轻代并且使用的是ParNew垃圾收集器。ParNew是一个Serial收集器的多线程版本，会使用多个CPU和线程完成垃圾收集工作（默认使用的线程数和CPU数相同，可以使用-XX：ParallelGCThreads参数限制）  ParNew (promotion failed): 7838K-&amp;gt;8391K(9216K) 7838K-&amp;gt;8391K(9216K)：单位是KB，三个参数分别为：GC前该内存区域(这里是年轻代)使用容量，GC后该内存区域使用容量，该内存区域总容量。  0.0029438 secs：该内存区域GC耗时，单位是秒  CMS: 8194K-&amp;gt;6659K(10240K), 0.0024311 secs] 11934K-&amp;gt;6659K(19456K)  8194K-&amp;gt;6659K(10240K)：GC前该内存区域(这里是老年代)使用容量变化，10240K表示该内存区域总容量， 11934K-&amp;gt;6659K(19456K)：三个参数分别为：堆区垃圾回收前的大小，堆区垃圾回收后的大小，堆区总大小  Times: user=0.02 sys=0.00, real=0.00 secs：分别表示用户态耗时，内核态耗时和总耗时 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;详细介绍&lt;/h2&gt; &lt;p&gt;可以看到出现了promotion failed，那什么情况下会出现promotion failed？&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;在进行Young GC时，Survivor Space放不下，对象只能放入老年代，而此时老年代也放不下时会出现&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;1.看代码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;byte[] array1 = new byte[4 * 1024 * 1024]; array1 = null; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这行代码直接分配了一个4MB的大对象，此时这个对象会直接进入老年代，接着array1不再引用这个对象&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5Eenloc2RuU0t1bkE0TGljQ3Q5SjVDRXJha01tN3dnWU5jRDA2bmJPTldlVWFSVkFHRGtyTEVSaWJYdy82NDA.png" alt="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5Eenloc2RuU0t1bkE0TGljQ3Q5SjVDRXJha01tN3dnWU5jRDA2bmJPTldlVWFSVkFHRGtyTEVSaWJYdy82NDA" title="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5Eenloc2RuU0t1bkE0TGljQ3Q5SjVDRXJha01tN3dnWU5jRDA2bmJPTldlVWFSVkFHRGtyTEVSaWJYdy82NDA" /&gt;&lt;/p&gt; &lt;p&gt;2.接着看下面的代码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;byte[] array2 = new byte[2 * 1024 * 1024]; byte[] array3 = new byte[2 * 1024 * 1024]; byte[] array4 = new byte[2 * 1024 * 1024]; byte[] array5 = new byte[128 * 1024]; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;连续分配了4个数组，其中3个是2MB的数组，1个是128KB的数组，如下图所示，全部会进入Eden区域中&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenlidDUxSXRtV2hRcU9RamliTHJaaFpUVGRhcnlZWXZVMWI3NmJicVVWMXlpYXg2YUxSS0pKSFRTUS82NDA.png" alt="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenlidDUxSXRtV2hRcU9RamliTHJaaFpUVGRhcnlZWXZVMWI3NmJicVVWMXlpYXg2YUxSS0pKSFRTUS82NDA" title="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenlidDUxSXRtV2hRcU9RamliTHJaaFpUVGRhcnlZWXZVMWI3NmJicVVWMXlpYXg2YUxSS0pKSFRTUS82NDA" /&gt;&lt;/p&gt; &lt;p&gt;3.接着会执行如下代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;byte[] array6 = new byte[2 * 1024 * 1024]; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时还能放得下2MB的对象吗？不可能了，因为Eden区已经放不下了。因此此时会直接触发一次Young GC。&lt;/p&gt; &lt;p&gt;4.我们看下面的GC日志：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ParNew (promotion failed): 7838K-&amp;gt;8391K(9216K), 0.0029438 secs &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这行日志显示了，Eden区原来是有 近8000KB的对象，但是回收之后发现一个都回收不掉，因为上述几个数组都被变量引用了一，所以一定会直接把这些存活的对象放入到老年代里去，但是此时老年代里已经有一个4MB的数组了，还能放的下3个2MB的数组和1个128KB的数组吗？&lt;/p&gt; &lt;p&gt;明显是不行的，此时一定会超过老年代的10MB大小。&lt;/p&gt; &lt;p&gt;5.所以此时我们看cms的gc日志：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;CMS: 8194K-&amp;gt;6659K(10240K), 0.0024311 secs] 11934K-&amp;gt;6659K(19456K), [Metaspace: 3121K-&amp;gt;3121K(1056768K)], 0.0055393 secs &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;大家可以清晰看到，此时执行了CMS垃圾回收器的Full GC，我们知道Full GC其实就是会对老年代进行Old GC， 同时一般会跟一次Young GC关联，还会触发一次元数据区（永久代）的GC。&lt;/p&gt; &lt;p&gt;在CMS Full GC之前，就已经触发过Young GC了，此时大家可以看到此时Young GC就已经有了，接着就是执行针对 老年代的Old GC，也就是如下日志：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;CMS: 8194K-&amp;gt;6659K(10240K), 0.0024311 secs &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;6.这里看到老年代从8MB左右的对象占用，变成了6MB左右的对象占用，这是怎么个过程呢？&lt;/p&gt; &lt;p&gt;很简单，一定是在Young GC之后，先把2个2MB的数组放入了老年代，此时要继续放1个2MB的数组和1个128KB的数组到老年代，一定会放不下，所以此时就会触发CMS的Full GC&lt;/p&gt; &lt;p&gt;然后此时就会回收掉其中的一个4MB的数组，因为他已经没人引用了&lt;/p&gt; &lt;p&gt;接着放入进去1个2MB的数组和1个128KB的数组&lt;/p&gt; &lt;p&gt;所以大家再看CMS的垃圾回收日志：CMS: 8194K-&amp;gt;6659K(10240K), 0.0024311 secs，他是从回收前的8MB变成了 6MB&lt;/p&gt; &lt;p&gt;最后在CMS Full GC执行完毕之后，其实年轻代的对象都进入了老年代，此时最后一行代码要在年轻代分配2MB的数组就可以成功了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenkzY0ZkSkkwTXQ2UVU4MFFneE9ON3ExdUM0d1hyejkxVDJtOTA5aDBVMVlDdzlxVmJKYUxNdWcvNjQw.png" alt="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenkzY0ZkSkkwTXQ2UVU4MFFneE9ON3ExdUM0d1hyejkxVDJtOTA5aDBVMVlDdzlxVmJKYUxNdWcvNjQw" title="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenkzY0ZkSkkwTXQ2UVU4MFFneE9ON3ExdUM0d1hyejkxVDJtOTA5aDBVMVlDdzlxVmJKYUxNdWcvNjQw" /&gt;&lt;/p&gt; &lt;h2&gt;补充知识&lt;/h2&gt; &lt;h4&gt;Young GC触发条件&lt;/h4&gt; &lt;p&gt;当年轻代Eden区域满的时候会触发一次Young GC&lt;/p&gt; &lt;h4&gt;Full GC触发条件&lt;/h4&gt; &lt;p&gt;Full GC用于清理整个堆空间。它的触发条件主要有以下几种：&lt;/p&gt; &lt;p&gt;1.显式调用&lt;code&gt;System.gc&lt;/code&gt;方法(建议JVM触发)。&lt;/p&gt; &lt;p&gt;2.元空间不足&lt;/p&gt; &lt;p&gt;3.年代空间不足，引起Full GC。这种情况比较复杂，有以下几种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;大对象直接进入老年代引起，由&lt;code&gt;-XX:PretenureSizeThreshold&lt;/code&gt;参数定义&lt;/li&gt; &lt;li&gt;Young GC时，经历过多次Young GC仍存在的对象进入老年代。&lt;/li&gt; &lt;li&gt;Young GC时，动态对象年龄判定机制会将对象提前转移老年代。年龄从小到大进行累加，当加入某个年龄段后，累加和超过survivor区域&lt;code&gt;-XX:TargetSurvivorRatio&lt;/code&gt;的时候，从这个年龄段往上的年龄的对象进入老年代&lt;/li&gt; &lt;li&gt;Young GC时，Eden和From Space区向To Space区复制时，大于To Space区可用内存，会直接把对象转移到老年代&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;4.JVM的空间分配担保机制可能会触发Full GC：&lt;/p&gt; &lt;p&gt;空间担保分配是指在发生Young GC之前，虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。&lt;/p&gt; &lt;p&gt;如果大于，则此次Young GC是安全的。&lt;/p&gt; &lt;p&gt;如果小于，则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。&lt;/p&gt; &lt;p&gt;如果HandlePromotionFailure=true，那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小，如果大于，则尝试进行一次Young GC，但这次Young GC依然是有风险的，失败后会重新发起一次Full gc；如果小于或者HandlePromotionFailure=false，则改为直接进行一次Full GC。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenltVnMzN2MwclI0ZEU2ajBidjVmaWFmRTRqTUJkQmpzZDY1VEJmVkpGbG8wemlheEZHdmpMU3FYdy82NDA.png" alt="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenltVnMzN2MwclI0ZEU2ajBidjVmaWFmRTRqTUJkQmpzZDY1VEJmVkpGbG8wemlheEZHdmpMU3FYdy82NDA" title="aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9oQzNvTkFKcVNSeE5uWFR2QmV5aWJjUTcwNlYzUm5EenltVnMzN2MwclI0ZEU2ajBidjVmaWFmRTRqTUJkQmpzZDY1VEJmVkpGbG8wemlheEZHdmpMU3FYdy82NDA" /&gt;&lt;/p&gt; &lt;h2&gt;GC Easy工具&lt;/h2&gt; &lt;p&gt;这里推荐一个gceasy(https://gceasy.io)工具，可以上传gc文件，然后他会利用可视化的界面来展现GC情况&lt;/p&gt; &lt;h2&gt;参考&lt;/h2&gt; &lt;p&gt;https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html&lt;/p&gt; &lt;p&gt;https://book.douban.com/subject/34907497/&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 19 Dec 2021 03:24:00 GMT</pubDate>
    </item>
    <item>
      <title>MAC 下 IDEA 启动SVN Eclipse项目</title>
      <link>https://maruifu.cn/article/218</link>
      <content:encoded>&lt;h2&gt;下载SVN&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;//安装brew  已经安装可以忽略 /bin/zsh -c &amp;quot;$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)&amp;quot; // 安装svn brew install svn &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果出现一下错误 ：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;svn: E170013: Commit failed (details follow): svn: E170013: Unable to connect  to a repository at URL '' svn: E230001: Server SSL certificate verification failed:  certificate issued for a different hostname &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解决办法：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Configure-&amp;gt;Preferences-&amp;gt;Version Control-&amp;gt;Subversion-&amp;gt;Enable interactive mode 勾选上 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置JDK&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;File-&amp;gt;Project Structure-&amp;gt;Project-&amp;gt;Project SDK-&amp;gt;New-&amp;gt;JDK 选择自己的JDK版本 File-&amp;gt;Project Structure-&amp;gt;Project-&amp;gt;Project language level-&amp;gt;JDK 选择自己的JDK版本 File-&amp;gt;Project Structure-&amp;gt;Module SDK-&amp;gt;选择自己的JDK版本 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置Tomcat&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;// 进入Tomcat 配置 Add Configuration -&amp;gt; Add New Configuration-&amp;gt;Tomcat Server-&amp;gt;Local //选择Tomcat Server-&amp;gt;Application server-&amp;gt; 选择自己的Tomcat //选择JDK Server-&amp;gt;JRE-&amp;gt; 选择自己的JDK版本 //添加编译目录 Deployment-&amp;gt;External Source-&amp;gt;选择项目的WebRoot目录  //其他问题 检查 文件对应属性是否正确 项目右键-&amp;gt;Mark Directory As &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 18 Dec 2021 15:01:00 GMT</pubDate>
    </item>
    <item>
      <title>Could not resolve XML resource [null] with public ID [null]</title>
      <link>https://maruifu.cn/article/217</link>
      <content:encoded>&lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;项目启动运行时发现报以下错误&lt;/p&gt; &lt;pre&gt;&lt;code&gt;严重: Parse error in application web.xml file at jndi:/localhost/WEB-INF/web.xml java.io.FileNotFoundException: Could not resolve XML resource [null] with public ID [null], system ID [webxml/web_dataclient.xml] and base URI [jndi:/localhost/WEB-INF/web.xml] to a known, local entity.  at org.apache.tomcat.util.descriptor.LocalResolver.resolveEntity(LocalResolver.java:154)  at com.sun.org.apache.xerces.internal.util.EntityResolver2Wrapper.resolveEntity(EntityResolver2Wrapper.java:176)  at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.resolveEntityAsPerStax(XMLEntityManager.java:991)  at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1206)  at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEntityReference(XMLDocumentFragmentScannerImpl.java:1908)  at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3067)  at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)  at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)  at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)  at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;原因&lt;/h2&gt; &lt;p&gt;servlet很多，方便管理希望能拆分文件 在web.xml文件里webapp标签上方加上&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;!DOCTYPE web-app         [&amp;lt;!ENTITY dataclient SYSTEM  &amp;quot;webxml/web_dataclient.xml&amp;quot;&amp;gt;          &amp;lt;!ENTITY document   SYSTEM  &amp;quot;webxml/web_document.xml&amp;quot;&amp;gt;          &amp;lt;!ENTITY project SYSTEM  &amp;quot;webxml/web_project.xml&amp;quot;&amp;gt;         ]&amp;gt;   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我测试了一下在tomcat 6下面这个方式确实是可以的，但是在tomcat7下面就报错了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java.io.FileNotFoundException: Could not resolve XML resource [null] with public ID [null], system ID [webxml/web_dataclient.xml] and base URI [jndi:/localhost/WEB-INF/web.xml] to a known, local entity. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后我又网上搜了一下，得知tomcat 7.0.52开始的版本才会出这个问题，是因为安全的考虑tomcat 7.0.52开始的版本把xmlBlockExterna属性默认为true，要解决这个问题把xmlBlockExterna设成false。&lt;/p&gt; &lt;p&gt;设置前&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;Context&amp;gt;     &amp;lt;!-- Default set of monitored resources --&amp;gt;     &amp;lt;WatchedResource&amp;gt;WEB-INF/web.xml&amp;lt;/WatchedResource&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;设置后&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;Context xmlBlockExternal=&amp;quot;false&amp;quot;&amp;gt;     &amp;lt;!-- Default set of monitored resources --&amp;gt;     &amp;lt;WatchedResource&amp;gt;WEB-INF/web.xml&amp;lt;/WatchedResource&amp;gt; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 18 Dec 2021 08:02:00 GMT</pubDate>
    </item>
    <item>
      <title>CentOS7.9下升级OpenSSL到OpenSSL 1.1.1k</title>
      <link>https://maruifu.cn/article/216</link>
      <content:encoded>&lt;h2&gt;系统环境介绍以及准备&lt;/h2&gt; &lt;h3&gt;查看系统版本&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@xmg-hk ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看openssl版本&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@xmg-hk ~]# openssl version  OpenSSL 1.0.2k-fips  26 Jan 2017 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;官网下载openssl-1.1.1k&lt;/h3&gt; &lt;p&gt;其他版本可参考下载： &lt;a href="https://www.openssl.org/source/openssl-1.1.1k.tar.gz" target="_blank"&gt; https://www.openssl.org/source/openssl-1.1.1k.tar.gz&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;详细操作步骤&lt;/h2&gt; &lt;h3&gt;先备份&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;mv /usr/bin/openssl /usr/bin/openssl.bak mv /usr/include/openssl /usr/include/openssl.bak &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;进入目录并编译&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;cd /usr/local/openssl-1.1.1k ./config --prefix=/usr/local/openssl make &amp;amp;&amp;amp; make install &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;建立链接&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl ln -s /usr/local/openssl/include/openssl /usr/include/openssl echo “/usr/local/openssl/lib” &amp;gt;&amp;gt; /etc/ld.so.conf ldconfig -v &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看是否升级成功&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;[root@xmg-hk ~]# openssl version  OpenSSL 1.1.1k  25 Mar 2021 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;说明：需要先进行备份，备份需要在&amp;quot;建立链接&amp;quot;操作以前完成。&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sun, 28 Nov 2021 06:20:00 GMT</pubDate>
    </item>
    <item>
      <title>CentOS安装noVNC，以Web方式交付VNC远程连接</title>
      <link>https://maruifu.cn/article/215</link>
      <content:encoded>&lt;h1&gt;CentOS安装noVNC，以Web方式交付VNC远程连接&lt;/h1&gt; &lt;h2&gt;什么是noVNC&lt;/h2&gt; &lt;p&gt;noVNC 是一个 HTML5 VNC 客户端，采用 HTML 5 WebSockets, Canvas 和 JavaScript 实现，noVNC 被普遍用在各大云计算、虚拟机控制面板中，比如 OpenStack Dashboard 和 OpenNebula Sunstone 都用的是 noVNC。 noVNC 采用 WebSockets 实现，但是目前大多数 VNC 服务器都不支持 WebSockets，所以 noVNC 是不能直接连接 VNC 服务器的，需要一个代理来做 WebSockets 和 TCP sockets 之间的转换。这个代理在 noVNC 的目录里，叫做 websockify 。&lt;/p&gt; &lt;h2&gt;实验环境&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;VMware Workstations&lt;/li&gt; &lt;li&gt;带桌面的CentOS7虚拟机&lt;/li&gt; &lt;li&gt;Windows 10 宿主机 + Google Chrome浏览器&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;关闭防火墙&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;setenforce 0 systemctl stop firewalld systemctl disable firewalld &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装noVNC&lt;/h2&gt; &lt;p&gt;安装依赖软件包&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;yum install -y epel* yum install -y git yum install -y tigervnc-server &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行以下命令并输入密码启动服务&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;vncserver :1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/1222343-20180728155333557-1692217544.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;安装noVNC&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;git clone git://github.com/kanaka/noVNC &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;创建安全连接（一路回车下去...）&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;cd ./noVNC/utils/ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注： VNC的默认会话不是安全的，需要创建一个安全的VNC连接。创建完毕的证书 &lt;code&gt;self.pem&lt;/code&gt; 需要放置到 &lt;code&gt;noVNC/utils&lt;/code&gt; 目录下，当启动 noVNC 时，websockify将自动装载证书。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/1222343-20180728155339738-1097592107.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;运行noVNC&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 在noVNC目录下，执行 ./utils/launch.sh --vnc localhost:5901 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/1222343-20180728155343773-677156333.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;测试连接&lt;/h2&gt; &lt;p&gt;在浏览器访问（注意替换成自己的IP地址） http://192.168.204.10:6080/vnc.html 输入密码，连接成功！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/1222343-20180728155349909-852543488.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;p&gt;当有请求访问vnc时，控制台会显示日志&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/1222343-20180728160214638-587767231.png" alt="img" title="img" /&gt;&lt;/p&gt; &lt;h2&gt;CentOS 7 的安装脚本&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#!/bin/bash  # stop selinux and iptables setenforce 0 systemctl stop firewalld systemctl disable firewalld  # install vncserver and git yum install -y epel* yum install tigervnc-server git -y vncserver :1 # 此时会提示输入密码  # download noVNC git clone git://github.com/kanaka/noVNC  # create secure connection cd ./noVNC/utils/ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem  # run noVNC cd ../ ./utils/launch.sh --vnc localhost:5901  # running &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 28 Nov 2021 06:05:31 GMT</pubDate>
    </item>
    <item>
      <title>群晖上查找占用带宽最大的进程的一种解决方案</title>
      <link>https://maruifu.cn/article/214</link>
      <content:encoded>&lt;p&gt;有天，在群晖的 Web 界面，看到网络监控那里，上传速度竟然有 5M/s，漏油器上也看到群晖的上传很高，也就是说流量上传到了外网，卧槽，这到底是哪个进程吃我带宽的，找出来我非得杀掉不可。 可惜，群晖自带的 “资源监控” 无法查看进程对网络的占用。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;因为群晖的系统，虽然是基于 debian，但是默认没有包管理器，无法通过 apt/yum 安装 iftop。不过群晖支持 docker。&lt;/p&gt; &lt;/blockquote&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;群晖 - 套件中心 - docker - 安装&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;ssh 连接群晖  &lt;a href="https://hub.docker.com/r/janten/iftop/" target="_blank"&gt;janten/iftop&lt;/a&gt; 这个镜像为我们提供了 iftop 命令&lt;/p&gt; &lt;pre&gt;&lt;code&gt; docker run -it --rm --net host janten/iftop -P -i eth0 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果本地没有没有这个镜像，会自动进行拉取，拉取镜像完成后，就可以看到 iftop 的运行界面&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;使用 netstat 定位进程&lt;/p&gt; &lt;pre&gt;&lt;code&gt; netstat -pantu | grep [port] &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Sun, 28 Nov 2021 06:01:00 GMT</pubDate>
    </item>
    <item>
      <title>在Docker搭建centos7远程桌面环境</title>
      <link>https://maruifu.cn/article/213</link>
      <content:encoded>&lt;h1&gt;&lt;/h1&gt; &lt;h2&gt;拉取获取centos7镜像&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;docker pull centos:7 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以从&lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fhub.docker.com%2F_%2Fcentos%3Ftab%3Dtags" target="_blank"&gt;https://hub.docker.com/_/centos?tab=tags&lt;/a&gt;查询拉取需要的镜像版本&lt;/p&gt; &lt;h2&gt;启动容器&lt;/h2&gt; &lt;p&gt;指定容器的名称为centos-desktop-vnc，并暴露宿主机的5901来连接vnc&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run --name centos-desktop-vnc --privileged -d -p 5901:5901 --ulimit memlock=-1 -td centos:7 /usr/sbin/init &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置desktop环境&lt;/h2&gt; &lt;h3&gt;进入容器环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;docker exec -it centos-desktop-vnc bash &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;默认的镜像不带desktop环境，需要手动安装&lt;/p&gt; &lt;h3&gt;查看支持的环境&lt;/h3&gt; &lt;p&gt;会出现很多结果，我们这里选择的是gnome环境&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum grouplist &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装gnome环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;yum groupinstall GNOME Desktop -y &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置系统默认的启动模式&lt;/h3&gt; &lt;p&gt;我们这里需要设置启动模式为图形化&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 获取当前启动模式 systemctl get-default  # 修改启动模式为图形化 systemctl set-default graphical.target  # 修改启动模式为命令行 systemctl set-default multi-user.target &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置vnc服务端&lt;/h2&gt; &lt;h3&gt;安装vnc server、vim、net-tools&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;yum -y install tigervnc-server tigervnc-server-module vim net-tools &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置vnc server&lt;/h3&gt; &lt;h4&gt;复制配置文件模板&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;cp /lib/systemd/system/vncserver@.service /lib/systemd/system/vncserver@:1.service &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;设置生效用户-修改配置文件&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;vim /lib/systemd/system/vncserver\@\:1.service &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;将配置文件的&lt;USER&gt;修改为root，由于root的home路径是/root，不是/home/root，因此注意修改PIDFILE的路径 改好之后如下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[Unit] Description=Remote desktop service (VNC) After=syslog.target network.target  [Service] Type=forking  # Clean any existing files in /tmp/.X11-unix environment ExecStartPre=/bin/sh -c '/usr/bin/vncserver -kill %i &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 || :' ExecStart=/usr/sbin/runuser -l root -c &amp;quot;/usr/bin/vncserver %i&amp;quot; PIDFile=/root/.vnc/%H%i.pid ExecStop=/bin/sh -c '/usr/bin/vncserver -kill %i &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 || :'  [Install] WantedBy=multi-user.target &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;修改vnc server密码&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;vncpasswd &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;生效vnc配置&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-undefined"&gt;systemctl daemon-reload &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;配置vnc开机启动&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 启动服务 systemctl start vncserver@:1 # 设为开机启动 systemctl enable vncserver@:1 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;检查vnc server是否启动&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-undefined"&gt;netstat -lnpt|grep Xvnc &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;出现下图结果说明启动成功&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@011ff517ebf7 /]# netstat -lnpt|grep Xvnc tcp        0      0 0.0.0.0:5901            0.0.0.0:*               LISTEN      4658/Xvnc            tcp        0      0 0.0.0.0:6001            0.0.0.0:*               LISTEN      4658/Xvnc            tcp6       0      0 :::5901                 :::*                    LISTEN      4658/Xvnc            tcp6       0      0 :::6001                 :::*                    LISTEN      4658/Xvnc            &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;关闭防火墙&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 关闭防火墙 systemctl stop firewalld # 禁止防火墙开机启动 systemctl disable firewalld &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;vnc客户端发起连接&lt;/h2&gt; &lt;h4&gt;下载vnc client 连接vnc server&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/image-20211128133356000.png" alt="image-20211128133356000" title="image-20211128133356000" /&gt;&lt;/p&gt; &lt;h4&gt;修改色彩质量&lt;/h4&gt; &lt;p&gt;初次连接上去可以看到桌面的色彩很模糊 解决办法是在建立好的远程连接点右键，选择 Properties，再选择 Options 选项卡，在 General 下面的 Picture quality 选择 High，保存。然后就可以看到图象变清晰了。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/28/image-20211128133608157.png" alt="image-20211128133608157" title="image-20211128133608157" /&gt;&lt;/p&gt; &lt;h2&gt;保存镜像&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# 1、查询container id，出现的第一个字符串就是container id，我这里是011ff517ebf7 docker ps -a | grep centos-desktop-vnc # 2、提交作为本地镜像 docker commit 011ff517ebf7 centos:7-vnc &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;中文安装&lt;/h2&gt; &lt;h3&gt;查看当前字符集&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;$ echo $LANG en_US.UTF-8 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装字符集&lt;/h3&gt; &lt;p&gt;使用locale命令看看当前系统所使用的字符集&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ locale LANG=en_US.UTF-8 LC_CTYPE=&amp;quot;en_US.UTF-8&amp;quot; LC_NUMERIC=&amp;quot;en_US.UTF-8&amp;quot; LC_TIME=&amp;quot;en_US.UTF-8&amp;quot; LC_COLLATE=&amp;quot;en_US.UTF-8&amp;quot; LC_MONETARY=&amp;quot;en_US.UTF-8&amp;quot; LC_MESSAGES=&amp;quot;en_US.UTF-8&amp;quot; LC_PAPER=&amp;quot;en_US.UTF-8&amp;quot; LC_NAME=&amp;quot;en_US.UTF-8&amp;quot; LC_ADDRESS=&amp;quot;en_US.UTF-8&amp;quot; LC_TELEPHONE=&amp;quot;en_US.UTF-8&amp;quot; LC_MEASUREMENT=&amp;quot;en_US.UTF-8&amp;quot; LC_IDENTIFICATION=&amp;quot;en_US.UTF-8&amp;quot; LC_ALL= &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看系统是否安装中文字符集支持&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# locale -a | grep CN bo_CN bo_CN.utf8 ug_CN ug_CN.utf8 zh_CN zh_CN.gb18030 zh_CN.gb2312 zh_CN.gbk zh_CN.utf8 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;若没有执行以下命令进行安装&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;yum install -y kde-l10n-Chinese yum reinstall -y glibc-common  #定义字符集 localedef -c -f UTF-8 -i zh_CN zh_CN.UFT-8 #确认载入成功 locale -a &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改系统字符集&lt;/h3&gt; &lt;p&gt;修改系统字符集的配置文件：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# echo 'LANG=&amp;quot;zh_CN.UTF-8&amp;quot;' &amp;gt; /etc/locale.conf # source /etc/locale.conf &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;验证字符集修改&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# echo $LANG zh_CN.UTF-8  # locale LANG=zh_CN.UTF-8 LC_CTYPE=&amp;quot;zh_CN.UTF-8&amp;quot; LC_NUMERIC=&amp;quot;zh_CN.UTF-8&amp;quot; LC_TIME=&amp;quot;zh_CN.UTF-8&amp;quot; LC_COLLATE=&amp;quot;zh_CN.UTF-8&amp;quot; LC_MONETARY=&amp;quot;zh_CN.UTF-8&amp;quot; LC_MESSAGES=&amp;quot;zh_CN.UTF-8&amp;quot; LC_PAPER=&amp;quot;zh_CN.UTF-8&amp;quot; LC_NAME=&amp;quot;zh_CN.UTF-8&amp;quot; LC_ADDRESS=&amp;quot;zh_CN.UTF-8&amp;quot; LC_TELEPHONE=&amp;quot;zh_CN.UTF-8&amp;quot; LC_MEASUREMENT=&amp;quot;zh_CN.UTF-8&amp;quot; LC_IDENTIFICATION=&amp;quot;zh_CN.UTF-8&amp;quot; LC_ALL= &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 28 Nov 2021 05:44:28 GMT</pubDate>
    </item>
    <item>
      <title>群晖使用Docker安装Minio</title>
      <link>https://maruifu.cn/article/212</link>
      <content:encoded>&lt;h2&gt;下载镜像&lt;/h2&gt; &lt;p&gt;在Docker的注册表中下载官方镜像&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123215011060.png" alt="image-20211123215011060" title="image-20211123215011060" /&gt;&lt;/p&gt; &lt;h2&gt;创建文件夹&lt;/h2&gt; &lt;p&gt;先在FIle Station中创建两个文件夹，用于存放配置和数据，如图所示&lt;/p&gt; &lt;p&gt;添加文件夹 &lt;code&gt;/docker/minio/data&lt;/code&gt; (可以与我相同的方式进行新建，也可以根据自己的习惯创建)，这个目录主要是用来存放我们的上传文件的。&lt;/p&gt; &lt;p&gt;添加文件夹 &lt;code&gt;/docker/minio/config&lt;/code&gt;，这个目录主要是用来存放我们的配置文件的。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123220150616.png" alt="image-20211123220150616" title="image-20211123220150616" /&gt;&lt;/p&gt; &lt;h2&gt;配置容器&lt;/h2&gt; &lt;p&gt;在下载完成后，在 &lt;code&gt;映像&lt;/code&gt; 中可以看到已经下载好的镜像文件，双击它来选择高就设置来配置容器。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123215436321.png" alt="image-20211123215436321" title="image-20211123215436321" /&gt;&lt;/p&gt; &lt;h3&gt;自动启动&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123215657289.png" alt="image-20211123215657289" title="image-20211123215657289" /&gt;&lt;/p&gt; &lt;h3&gt;存储空间&lt;/h3&gt; &lt;p&gt;添加映射的文件夹&lt;/p&gt; &lt;p&gt;&lt;code&gt;/docker/minio/data&lt;/code&gt;配置装载路径 &lt;code&gt;/data&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;/docker/minio/config&lt;/code&gt; 配置装载路径 &lt;code&gt;/root/.minio&lt;/code&gt; (其中 &lt;code&gt;.&lt;/code&gt; 千万不能漏)&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123220233644.png" alt="image-20211123220233644" title="image-20211123220233644" /&gt;&lt;/p&gt; &lt;h3&gt;端口设置&lt;/h3&gt; &lt;p&gt;选择 &lt;code&gt;端口设置&lt;/code&gt; 设置本地端口为 &lt;code&gt;9000&lt;/code&gt; （当然其他端口也可以，这也是我们的访问端口，如果此处修改了，访问时就使用修改后的端口进行访问。&lt;/p&gt; &lt;p&gt;一个静态端口，如果不配置此端口程序可以系统，没有报错，但是访问不到后台界面。这个端口可以是自己未占用的任意端口我用的9001端口&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123221124962.png" alt="image-20211123221124962" title="image-20211123221124962" /&gt;&lt;/p&gt; &lt;h3&gt;环境&lt;/h3&gt; &lt;p&gt;在命令出添加一下命令，其中端口号为刚才设置的静态端口号&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server --console-address '0.0.0.0:9001' /data &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123221316614.png" alt="image-20211123221316614" title="image-20211123221316614" /&gt;&lt;/p&gt; &lt;h2&gt;登录后台&lt;/h2&gt; &lt;p&gt;通过 &lt;code&gt;http://NAS的IP:9000&lt;/code&gt; 访问到minio的登录页面，默认账号 &lt;code&gt;minioadmin&lt;/code&gt; ， &lt;code&gt;minioadmin&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123222039104.png" alt="image-20211123222039104" title="image-20211123222039104" /&gt;&lt;/p&gt; &lt;h2&gt;修改账号密码&lt;/h2&gt; &lt;h3&gt;修改配置文件&lt;/h3&gt; &lt;p&gt;通过 &lt;code&gt;File Station&lt;/code&gt; 找到文件 &lt;code&gt;docker/minIO/data/.minio.sys/config/config.json&lt;/code&gt; 文件&lt;/p&gt; &lt;p&gt;找到其中的内容 &lt;code&gt;credentials&lt;/code&gt; ，修改 &lt;code&gt;access_key&lt;/code&gt; 的 &lt;code&gt;value&lt;/code&gt; 为你自己想要的，譬如：&lt;code&gt;xiaomage-user&lt;/code&gt;，修改其中 &lt;code&gt;secret_key&lt;/code&gt; 的 &lt;code&gt;value&lt;/code&gt; 为你自己想要的，譬如：&lt;code&gt;xiaomage-password&lt;/code&gt;，最后变成如下（为了提高可读性，我这边对文件进行了格式化处理）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;quot;credentials&amp;quot;: {         &amp;quot;_&amp;quot;: [             {                 &amp;quot;key&amp;quot;: &amp;quot;access_key&amp;quot;,                 &amp;quot;value&amp;quot;: &amp;quot;xiaomage-user&amp;quot;             },             {                 &amp;quot;key&amp;quot;: &amp;quot;secret_key&amp;quot;,                 &amp;quot;value&amp;quot;: &amp;quot;xiaomage-password&amp;quot;             }         ]     }, &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;重新启动&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/23/image-20211123223248537.png" alt="image-20211123223248537" title="image-20211123223248537" /&gt;&lt;/p&gt; &lt;p&gt;此时重新访问 可以使用新的账号密码！&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 23 Nov 2021 14:36:11 GMT</pubDate>
    </item>
    <item>
      <title>QQ，微信，支付宝二维码三合一</title>
      <link>https://maruifu.cn/article/211</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;其实这玩意早就有了，各大网站也可搜到，但是为了避免不必要的风险，以及为自己长久使用，可以考虑自己组合一个通用的二维码&lt;/p&gt; &lt;p&gt;~~做好了给老爸收款用~~&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;原理&lt;/h2&gt; &lt;p&gt;通过user-agent判断，对不同的应用跳转不同的链接&lt;/p&gt; &lt;h2&gt;准备工作&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;微信收款二维码&lt;/li&gt; &lt;li&gt;QQ收款二维码&lt;/li&gt; &lt;li&gt;支付宝收款码&lt;/li&gt; &lt;li&gt;一个域名+服务器&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;部分可能会用上的网址：&lt;/p&gt; &lt;p&gt;&lt;a href="http://www.wwei.cn/" target="_blank"&gt;二维码生成&lt;/a&gt; &lt;a href="https://jiema.wwei.cn/url.html" target="_blank"&gt;二维码解析&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;方法&lt;/h2&gt; &lt;p&gt;本来以为很简单，果断去解码然后填入地址，然后迫不及待用qq扫描二维码&lt;/p&gt; &lt;p&gt;果不其然，腾讯一向反人类，事实证明，只有支付宝扫码能够直接跳转支付页面，而QQ和wechat都不行&lt;/p&gt; &lt;p&gt;于是，把动手的活儿交给观众们&lt;/p&gt; &lt;p&gt;这时，我们利用跳转把各个支付二维码图片展现在屏幕面前&lt;/p&gt; &lt;p&gt;顺手制作了三张二维码 ~~佩服本人的聪明才智~~&lt;/p&gt; &lt;center class="half"&gt; &lt;img src="https://img.maruifu.com/images/2021/11/03/3671635948850_.pic.jpg" alt="3671635948850_.pic" style="zoom: 25%;" /&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/3691635948965_.pic.jpg" alt="3691635948965_.pic" style="zoom:25%;" /&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/3681635948936_.pic.jpg" alt="3681635948936_.pic" style="zoom:25%;" /&gt; &lt;/center&gt; 一切都准备好了，然后开始撸代码（保存index.php） &lt;pre&gt;&lt;code&gt;&amp;lt;?php   if (strstr($_SERVER['HTTP_USER_AGENT'], 'AlipayClient')) {      $AlipayURL = 'https://qr.alipay.com/fkx12586elzhiss95ftmtee';     header(&amp;quot;location: &amp;quot; . $AlipayURL); }  elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger')) {   echo '&amp;lt;img style=&amp;quot;width:100%;&amp;quot; src=&amp;quot;wx.jpg&amp;quot;&amp;gt;&amp;lt;/img&amp;gt;'; }  elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'QQ')) {  echo '&amp;lt;img style=&amp;quot;width:100%;&amp;quot; src=&amp;quot;qq.jpg&amp;quot;&amp;gt;&amp;lt;/img&amp;gt;'; }  else {      echo &amp;quot;请使用支付宝、微信、QQ客户端扫码付款&amp;quot;; } ?&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中&lt;code&gt;MicroMessenger&lt;/code&gt;是微信客户端，其中的链接和图片自行修改&lt;/p&gt; &lt;p&gt;保存上传网站如图所示&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/image-20211103225418292.png" alt="image-20211103225418292" title="image-20211103225418292" /&gt;&lt;/p&gt; &lt;p&gt;最后一步，生成一个你网站的二维码即可&lt;/p&gt; &lt;h2&gt;成果&lt;/h2&gt; &lt;p&gt;&lt;a href="pay.mm.mw" target="_blank"&gt;点击链接 pay.mm.mw&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/f2909009ceb37cd06e9e896ff040f393.png" alt="下载" title="下载" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 03 Nov 2021 15:04:52 GMT</pubDate>
    </item>
    <item>
      <title>IntelliJ IDEA如何把java源代码打包成jar包</title>
      <link>https://maruifu.cn/article/210</link>
      <content:encoded>&lt;p&gt;1.我们点击菜单栏中的File–&amp;gt;选择Project Structure…(Ctrl+Alt+Shift+S)进入到设置页面，或者在IntelliJ IDEA页面右侧点击右上角的按钮也可到配置页面。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.56.36.png" alt="截屏2021-11-03 下午9.56.36" title="截屏2021-11-03 下午9.56.36" /&gt;&lt;/p&gt; &lt;p&gt;2.我们在Project Structure的设置页面选择Artifacts，然后点击右侧的绿色“+”号，接下来我们选择JAR–&amp;gt;From modules with dependencies…进入到设置页面。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.57.03.png" alt="截屏2021-11-03 下午9.57.03" title="截屏2021-11-03 下午9.57.03" /&gt;&lt;/p&gt; &lt;p&gt;3.接下来是最重要的步骤，下图中modules一般会自动生成，Main class我们需要点击右侧的按钮，找到项目工程中含有main方法的那个类。JAR文件设置我们选择extract to the target JAR，打包时可将代码依赖的包也打入。最后META-INF/MANIFEST.MF的设置，我们选择项目的根路径即可。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.57.16.png" alt="截屏2021-11-03 下午9.57.16" title="截屏2021-11-03 下午9.57.16" /&gt;&lt;/p&gt; &lt;p&gt;4.需要注意的是，如果项目中已经有META-INF/MANIFEST.MF文件的话是设置不成功的，我们需要先找到项目在磁盘中的位置，然后删掉该文件再重复上述的步骤才可以。&lt;/p&gt; &lt;p&gt;5.设置完成后我们便可以执行生成jar包的操作了，点击菜单栏中的Build–&amp;gt;Build Artifacts…，然后我们在下方代码区便可以看到Build Artifact 选择之前创建的Artifacts然后点击Build即可。&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.57.40.png" alt="截屏2021-11-03 下午9.57.40" title="截屏2021-11-03 下午9.57.40" /&gt;t,&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.58.18.png" alt="截屏2021-11-03 下午9.58.18" title="截屏2021-11-03 下午9.58.18" /&gt;&lt;/p&gt; &lt;p&gt;6.在Build JAR文件后下方会有已完成的提示，我们找到项目所在的磁盘位置，在项目根路径下的out/artifacts/目录下便是新生成的jar文件。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/03/2021-11-03-9.58.32.png" alt="截屏2021-11-03 下午9.58.32" title="截屏2021-11-03 下午9.58.32" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 03 Nov 2021 13:59:23 GMT</pubDate>
    </item>
    <item>
      <title>ssh scp出现Permission denied</title>
      <link>https://maruifu.cn/article/209</link>
      <content:encoded>&lt;p&gt;在ssh远程连接192.168.2.1这台主机时，出现Permission denied，please try again。同样scp 远程拷贝也出现Permission denied，please try again。遇到这样的情况，如果不是密码错误，并且192.168.2.1的sshd服务开启，则需要修改这台主机的配置文件：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim /etc/ssh/sshd_config # 修改PermitRootLogin yes # 重启服务 systemctl restart sshd  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 03 Nov 2021 13:46:46 GMT</pubDate>
    </item>
    <item>
      <title>什么是NIO？NIO的原理是什么机制？</title>
      <link>https://maruifu.cn/article/208</link>
      <content:encoded>&lt;h2&gt;核心区别&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;NIO是以块的方式处理数据，但是IO是以最基础的字节流的形式去写入和读出的。所以在效率上的话，肯定是NIO效率比IO效率会高出很多。&lt;/li&gt; &lt;li&gt;NIO不在是和IO一样用OutputStream和InputStream 输入流的形式来进行处理数据的，但是又是基于这种流的形式，而是采用了通道和缓冲区的形式来进行处理数据的。&lt;/li&gt; &lt;li&gt;还有一点就是NIO的通道是可以双向的，但是IO中的流只能是单向的。&lt;/li&gt; &lt;li&gt;还有就是NIO的缓冲区（其实也就是一个字节数组）还可以进行分片，可以建立只读缓冲区、直接缓冲区和间接缓冲区，只读缓冲区很明显就是字面意思，直接缓冲区是为加快 I/O 速度，而以一种特殊的方式分配其内存的缓冲区。&lt;/li&gt; &lt;li&gt;NIO比传统的BIO核心区别就是，NIO采用的是多路复用的IO模型，普通的IO用的是阻塞的IO模型，两个之间的效率肯定是多路复用效率更高&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;通道&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象（通道）。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中；同样地，从通道中读取的任何数据都要读到缓冲区中。Channel是一个对象，可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较，通道就像是流。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;正如前面提到的，所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中，相反，您是将数据写入包含一个或者多个字节的缓冲区。同样，您不会直接从通道中读取字节，而是将数据从通道读入缓冲区，再从缓冲区获取这个字节。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;缓冲区&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Buffer 是一个对象， 它包含一些要写入或者刚读出的数据。在 NIO 中加入 Buffer 对象，体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中，您将数据直接写入或者将数据直接读到 Stream 对象中&lt;/li&gt; &lt;li&gt;在 NIO 库中，所有数据都是用缓冲区处理的。在读取数据时，它是直接读到缓冲区中的。在写入数据时，它是写入到缓冲区中的。任何时候访问 NIO 中的数据，您都是将它放到缓冲区中。&lt;/li&gt; &lt;li&gt;缓冲区实质上是一个数组。通常它是一个字节数组，但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问，而且还可以跟踪系统的读/写进程&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;缓冲区类型&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-ByteBuffer"&gt;CharBuffer  ShortBuffer  IntBuffer   LongBuffer  FloatBuffer  DoubleBuffer &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;NIO的工作原理&lt;/h2&gt; &lt;h3&gt;缓冲区的工作机制&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;capacity 缓冲区数组的总长度&lt;/p&gt; &lt;p&gt;position 下一个要操作的数据元素的位置&lt;/p&gt; &lt;p&gt;limit 缓冲区数组中不可操作的下一个元素的位置，limit&amp;lt;=capacity&lt;/p&gt; &lt;p&gt;mark 用于记录当前 position 的前一个位置或者默认是 0&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;1.这一步其实是当我们刚开始初始化这个buffer数组的时候，开始默认是这样的&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/01/2021-11-01-9.04.03.png" alt="截屏2021-11-01 下午9.04.03" title="截屏2021-11-01 下午9.04.03" /&gt;&lt;/p&gt; &lt;p&gt;2、但是当你往buffer数组中开始写入的时候几个字节的时候就会变成下面的图，position会移动你数据的结束的下一个位置，这个时候你需要把buffer中的数据写到channel管道中，所以此时我们就需要用这个&lt;code&gt;buffer.flip()&lt;/code&gt;方法&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/01/2021-11-01-9.04.25.png" alt="截屏2021-11-01 下午9.04.25" title="截屏2021-11-01 下午9.04.25" /&gt;&lt;/p&gt; &lt;p&gt;3、当你调用完2中的方法时，这个时候就会变成下面的图了，这样的话其实就可以知道你刚刚写到buffer中的数据是在position----&amp;gt;limit之间，然后下一步调用&lt;code&gt;clear（）&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/01/2021-11-01-9.04.39.png" alt="截屏2021-11-01 下午9.04.39" title="截屏2021-11-01 下午9.04.39" /&gt;&lt;/p&gt; &lt;p&gt;4、这时底层操作系统就可以从缓冲区中正确读取这 5 个字节数据发送出去了。在下一次写数据之前我们在调一下 clear() 方法。缓冲区的索引状态又回到初始位置。（其实这一步有点像IO中的把转运字节数组&lt;code&gt;char[] buf = new char[1024]&lt;/code&gt;不足1024字节的部分给强制刷新出去的意思）&lt;/p&gt; &lt;h3&gt;说明&lt;/h3&gt; &lt;p&gt;1、这里还要说明一下 mark，当我们调用&lt;code&gt;mark()&lt;/code&gt;时，它将记录当前 position 的前一个位置，当我们调用 reset 时，position 将恢复 mark 记录下来的值&lt;/p&gt; &lt;p&gt;2.clear()方法会：清空整个缓冲区。position将被设回0，limit被设置成 capacity的值（这个个人的理解就是当你在flip（）方法的基础上已经记住你写入了多少字节数据，直接把position到limit之间的也就是你写入已经记住的数据给“复制”到管道中）&lt;/p&gt; &lt;p&gt;3.当你把缓冲区的数局写入到管道中的时候，你需要调用flip()方法将Buffer从写模式切换到读模式，调用flip()方法会将position设回0，并将limit设置成之前position的值。buf.flip();（其实我个人理解的就相当于先记住缓冲区缓冲了多少数据）&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/01/2021-11-01-9.05.18.png" alt="截屏2021-11-01 下午9.05.18" title="截屏2021-11-01 下午9.05.18" /&gt;&lt;/p&gt; &lt;h3&gt;工作代码示例&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public void selector() throws IOException {   //先给缓冲区申请内存空间           ByteBuffer buffer = ByteBuffer.allocate(1024);        //打开Selector为了它可以轮询每个 Channel 的状态           Selector selector = Selector.open();           ServerSocketChannel ssc = ServerSocketChannel.open();           ssc.configureBlocking(false);//设置为非阻塞方式           ssc.socket().bind(new InetSocketAddress(8080));           ssc.register(selector, SelectionKey.OP_ACCEPT);//注册监听的事件           while (true) {               Set selectedKeys = selector.selectedKeys();//取得所有key集合               Iterator it = selectedKeys.iterator();               while (it.hasNext()) {                   SelectionKey key = (SelectionKey) it.next();                   if ((key.readyOps() &amp;amp; SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {                       ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();                    SocketChannel sc = ssChannel.accept();//接受到服务端的请求                       sc.configureBlocking(false);                       sc.register(selector, SelectionKey.OP_READ);                       it.remove();                   } else if                    ((key.readyOps() &amp;amp; SelectionKey.OP_READ) == SelectionKey.OP_READ) {                       SocketChannel sc = (SocketChannel) key.channel();                       while (true) {                           buffer.clear();                           int n = sc.read(buffer);//读取数据                           if (n &amp;lt;= 0) {                               break;                           }                           buffer.flip();                       }                       it.remove();                   }               }           }   }   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;NIO的示意图&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/11/01/2021-11-01-9.05.56.png" alt="截屏2021-11-01 下午9.05.56" title="截屏2021-11-01 下午9.05.56" /&gt;&lt;/p&gt; &lt;h2&gt;NIO和Netty的工作模型对比？&lt;/h2&gt; &lt;h3&gt;NIO的工作流程步骤&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;首先是先创建ServerSocketChannel 对象，和真正处理业务的线程池&lt;/li&gt; &lt;li&gt;然后给刚刚创建的ServerSocketChannel 对象进行绑定一个对应的端口，然后设置为非阻塞&lt;/li&gt; &lt;li&gt;然后创建Selector对象并打开，然后把这Selector对象注册到ServerSocketChannel 中，并设置好监听的事件，监听 SelectionKey.OP_ACCEPT&lt;/li&gt; &lt;li&gt;接着就是Selector对象进行死循环监听每一个Channel通道的事件，循环执行 Selector.select() 方法，轮询就绪的 Channel&lt;/li&gt; &lt;li&gt;从Selector中获取所有的SelectorKey（这个就可以看成是不同的事件），如果SelectorKey是处于 OP_ACCEPT 状态，说明是新的客户端接入，调用 ServerSocketChannel.accept 接收新的客户端。&lt;/li&gt; &lt;li&gt;然后对这个把这个接受的新客户端的Channel通道注册到ServerSocketChannel上，并且把之前的OP_ACCEPT 状态改为SelectionKey.OP_READ读取事件状态，并且设置为非阻塞的，然后把当前的这个SelectorKey给移除掉，说明这个事件完成了&lt;/li&gt; &lt;li&gt;如果第5步的时候过来的事件不是OP_ACCEPT 状态，那就是OP_READ读取数据的事件状态，然后调用本文章的上面的那个读取数据的机制就可以了&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;Netty的工作流程步骤&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。&lt;/li&gt; &lt;li&gt;设置 ServerBootstrap 的属性：线程组、SO_BACKLOG 选项，设置 NioServerSocketChannel 为 Channel，设置业务处理 Handler&lt;/li&gt; &lt;li&gt;绑定端口，启动服务器程序。&lt;/li&gt; &lt;li&gt;在业务处理 TimeServerHandler 中，读取客户端发送的数据，并给出响应&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;两者之间的区别：&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;OP_ACCEPT 的处理被简化，因为对于 accept 操作的处理在不同业务上都是一致的。&lt;/li&gt; &lt;li&gt;在 NIO 中需要自己构建 ByteBuffer 从 Channel 中读取数据，而 Netty 中数据是直接读取完成存放在 ByteBuf 中的。相当于省略了用户进程从内核中复制数据的过程。&lt;/li&gt; &lt;li&gt;在 Netty 中，我们看到有使用一个解码器 FixedLengthFrameDecoder，可以用于处理定长消息的问题，能够解决 TCP 粘包读半包问题，十分方便。&lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Mon, 01 Nov 2021 13:08:00 GMT</pubDate>
    </item>
    <item>
      <title>Java volatile关键字</title>
      <link>https://maruifu.cn/article/207</link>
      <content:encoded>&lt;h2&gt;Java内存模型&lt;/h2&gt; &lt;p&gt;Java 内存模型（JMM）是一种抽象的概念，并不真实存在，它描述了一组规则或规范，通过这组规范定义了程序中各个变量（包括实例字段、静态字段和构成数组对象的元素）的访问方式。试图屏蔽各种硬件和操作系统的内存访问差异，以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。&lt;/p&gt; &lt;p&gt;注意JMM与JVM内存区域划分的区别：&lt;/p&gt; &lt;p&gt;JMM描述的是一组规则，围绕原子性、有序性和可见性展开；&lt;/p&gt; &lt;p&gt;相似点：存在共享区域和私有区域&lt;/p&gt; &lt;h2&gt;主内存与工作内存&lt;/h2&gt; &lt;p&gt;处理器上的寄存器的读写的速度比内存快几个数量级，为了解决这种速度矛盾，在它们之间加入了高速缓存。&lt;/p&gt; &lt;p&gt;加入高速缓存带来了一个新的问题：缓存一致性。如果多个缓存共享同一块主内存区域，那么多个缓存的数据可能会不一致，需要一些协议来解决这个问题。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/30/image-20210830150540932.png" alt="image-20210830150540932" title="image-20210830150540932" /&gt;&lt;/p&gt; &lt;p&gt;所有的变量都&lt;strong&gt;存储在主内存中，每个线程还有自己的工作内存&lt;/strong&gt;，工作内存存储在高速缓存或者寄存器中，保存了该线程使用的变量的主内存副本拷贝。&lt;/p&gt; &lt;p&gt;线程只能直接操作工作内存中的变量，不同线程之间的变量值传递需要通过主内存来完成。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/30/image-20210830150606986.png" alt="image-20210830150606986" title="image-20210830150606986" /&gt;&lt;/p&gt; &lt;h2&gt;数据存储类型以及操作方式&lt;/h2&gt; &lt;p&gt;方法中的基本类型本地变量将直接存储在工作内存的栈帧结构中；&lt;/p&gt; &lt;p&gt;引用类型的本地变量：引用存储在工作内存，实际存储在主内存；&lt;/p&gt; &lt;p&gt;成员变量、静态变量、类信息均会被存储在主内存中；&lt;/p&gt; &lt;p&gt;主内存共享的方式是线程各拷贝一份数据到工作内存中，操作完成后就刷新到主内存中。&lt;/p&gt; &lt;h2&gt;内存间交互操作&lt;/h2&gt; &lt;p&gt;Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/30/image-20210830150641535.png" alt="image-20210830150641535" title="image-20210830150641535" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;read：把一个变量的值从主内存传输到工作内存（CPU级别的缓存）中&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;load：在 read 之后执行，把 read 得到的值放入工作内存的变量副本中&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;use：把工作内存中一个变量的值传递给执行引擎&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;assign：把一个从执行引擎接收到的值赋给工作内存的变量&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;store：把工作内存的一个变量的值传送到主内存中&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;write：在 store 之后执行，把 store 得到的值放入主内存的变量中&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;lock：作用于主内存的变量&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;unlock&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;内存模型三大特性&lt;/h2&gt; &lt;h3&gt;原子性&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;// 先看一下代码： public class Main {     private static int cnt = 0;     public static void main(String[] args) {         Runnable runnable = () -&amp;gt; {             for (int j = 0; j &amp;lt; 100; j++) {                 cnt++;                 try {                     Thread.sleep(30);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }             System.out.println(cnt);         };         new Thread(runnable).start();         new Thread(runnable).start();     } }  // 发现上面代码输出并没有等于200； &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性，例如对一个 int 类型的变量执行 assign 赋值操作，这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据（long，double）的读写操作划分为两次 32 位的操作来进行，即 load、store、read 和 write 操作可以不具备原子性。&lt;/p&gt; &lt;p&gt;有一个错误认识就是，int 等原子性的类型在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中，cnt 属于 int 类型变量，2 个线程对它进行自增操作之后，得到的值为 1 而不是 2。&lt;/p&gt; &lt;p&gt;为了方便讨论，将内存间的交互操作简化为 3 个：load、assign、store。&lt;/p&gt; &lt;p&gt;下图演示了两个线程同时对 cnt 进行操作，load、assign、store 这一系列操作整体上看不具备原子性，那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存，T2 依然可以读入旧值。可以看出，这两个线程虽然执行了两次自增运算，但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/30/image-20210830152542146.png" alt="image-20210830152542146" title="image-20210830152542146" /&gt;&lt;/p&gt; &lt;p&gt;AtomicInteger 能保证多个线程修改的原子性。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/30/image-20210830152836133.png" alt="image-20210830152836133" title="image-20210830152836133" /&gt;&lt;/p&gt; &lt;p&gt;使用 AtomicInteger 重写之前线程不安全的代码之后得到以下线程安全实现：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import java.util.concurrent.atomic.AtomicInteger; public class Main {     private static AtomicInteger cnt = new AtomicInteger();     public static void main(String[] args) {         Runnable runnable = () -&amp;gt; {             for (int j = 0; j &amp;lt; 100; j++) {                 cnt.getAndIncrement();                 try {                     Thread.sleep(30);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }             System.out.println(cnt.get());         };         new Thread(runnable).start();         new Thread(runnable).start();     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;除了使用原子类之外还可以使用  synchronized 互斥锁来保证操作的原子性。它对应的内存间交互操作为：lock 和 unlock，在虚拟机实现上对应的字节码指令为 monitorenter 和 monitorexit。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Main1 {     private static int cnt = 0;     public synchronized void add() {         cnt++;     }     public static void main(String[] args) {         Main1 main1 =new Main1();         Runnable runnable = () -&amp;gt; {             for (int j = 0; j &amp;lt; 100; j++) {                 main1.add();                 try {                     Thread.sleep(30);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }             System.out.println(cnt);         };         new Thread(runnable).start();         new Thread(runnable).start();     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;可见性&lt;/h3&gt; &lt;p&gt;可见性指当一个线程修改了共享变量的值，其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值来实现可见性的。&lt;/p&gt; &lt;p&gt;主要有有三种实现可见性的方式：&lt;/p&gt; &lt;p&gt;volatile，会&lt;strong&gt;强制&lt;/strong&gt;将该变量自己和当时其他变量的状态都&lt;strong&gt;刷出缓存&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;synchronized，对一个变量执行 unlock 操作之前，必须把变量值同步回主内存。&lt;/p&gt; &lt;p&gt;final，被 final 关键字修饰的字段在构造器中一旦初始化完成，并且没有发生 this 逃逸（其它线程通过 this 引用访问到初始化了一半的对象），那么其它线程就能看见 final 字段的值。&lt;/p&gt; &lt;p&gt;对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰，不能解决线程不安全问题，因为 volatile 并不能保证操作的原子性。&lt;/p&gt; &lt;h3&gt;有序性&lt;/h3&gt; &lt;p&gt;有序性是指：在本线程内观察，所有操作都是有序的。在一个线程观察另一个线程，所有操作都是无序的，无序是因为发生了指令重排序。在 Java 内存模型中，允许编译器和处理器对指令进行重排序，重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。&lt;/p&gt; &lt;p&gt;简单来说：对于代码有个问题就是指令重排序，编译器和指令器有时候为了提高代码执行效率会将指令重新排序。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;flag = false;  //线程1： //准备资源 prepare()  flag = true;  //线程2： while(!flag){    Thread.sleep(1000) }  //基于准备好的资源执行操作 execute() &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;指令重排序后，让flag=true 先执行了，会导致线程2 直接跳过while等待，执行某段代码，结果prepare()方法还没有执行，资源没有准备好，此时就会导致代码逻辑出现异常！&lt;/p&gt; &lt;p&gt;JMM 内部的实现通常是依赖于所谓的&lt;strong&gt;内存屏障&lt;/strong&gt;，通过&lt;strong&gt;禁止某些重排序&lt;/strong&gt;的方式，提供内存&lt;strong&gt;可见性保证&lt;/strong&gt;，也就是实现了&lt;strong&gt;各种 happen-before 规则&lt;/strong&gt;。与此同时，更多复杂度在于，需要尽量确保各种编译器、各种体系结构的处理器，都能够提供一致的行为。&lt;/p&gt; &lt;p&gt;先行发生原则(Happen-Before)&lt;/p&gt; &lt;p&gt;JSR-133内存模型使用先行发生原则在Java内存模型中保证多线程操作&lt;strong&gt;可见性&lt;/strong&gt;的机制，也是对早期语言规范中含糊的可见性概念的一个精确定义。上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外，JVM 还规定了先行发生原则，让一个操作&lt;strong&gt;无需控制&lt;/strong&gt;就能先于另一个操作完成。&lt;/p&gt; &lt;p&gt;由于&lt;strong&gt;指令重排序&lt;/strong&gt;的存在，两个操作之间有happen-before关系，&lt;strong&gt;并不意味着前一个操作必须要在后一个操作之前执行。 仅仅要求前一个操作的执行结果对于后一个操作是可见的，并且前一个操作 按顺序&lt;/strong&gt; 排在第二个操作之前。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;单一线程原则（程序员顺序规则）Single Thread rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;在一个线程内，按照代码顺序，书写在程序前面的操作先行发生于书写后面的操作。&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;管程锁定规则（监视器锁规则）Monitor Lock Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;一个 unlock（解锁） 操作先行发生于后面对同一个锁的 lock（加锁）操作。比如代码里面先对一个lock.lock()然后lock.unlock(),然后lock.lock()&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;volatile 变量规则 Volatile Variable Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;线程启动规则Thread Start Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。比如Thread.start() interrupt()&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;线程加入规则 Thread Join Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;Thread 对象的结束先行发生于 join() 方法返回。&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;线程中断规则 Thread Interruption Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过 interrupted() 方法检测到是否有中断发生。&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;对象终结规则 Finalizer Rule&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;一个对象的初始化完成（构造函数执行结束）先行发生于它的 finalize() 方法的开始。&lt;/code&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;传递性 Transitivity&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;如果操作 A 先行发生于操作 B，操作 B 先行发生于操作 C，那么操作 A 先行发生于操作 C。&lt;/code&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;总结：这些规则制定了在一些特殊情况下，不允许编译机，指令器对你写的代码进行指令重排，必须保证你的代码的有序性&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;strong&gt;指令重排序的条件&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;在单线程环境下不能改变程序的运行结果；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;存在数据依赖关系的不允许重排序；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;无法通过Happens-before原则推到出来的，才能进行指令的重排序。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;volatile&lt;/h2&gt; &lt;h3&gt;可见性&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public class Main {     private  volatile static int i = 0;     public static void main(String[] args)  {         new Thread(()-&amp;gt;{              i++ ;         }).start();         new Thread(()-&amp;gt;{             // volatile 修饰后 当线程1 操作i++ 刷新到主内存中后，会让线程2工作内存的缓存失效             // 此时会读取到 i=1             while (i ==0){ Thread.sleep(1000); }             i++ ;         }).start();     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;常用场景：一个系统 中间连接各种中间件系统，不能直接结束主进程，在结束主进程的时候要把里面的各个中间件系统关闭后，在结束主进程，不然可能会消息丢失，或者数据不一致等现象.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Kafka{     private volatile boolean running =true ;               // 这是一个接口     public void shutdown(){     //关闭这个系统了，shutdown.sh脚本，来调用这个shutdown接口         // 最后运行状态置为false         running =false ;     }          public static void main(){          //启动kafka , rocketmq ,会运行一大堆代码，中间件系统不能直接停掉          Kafka kafka= new Kafka();                    // 监控kafka 是否关闭 ，未关闭则需要等待！          while(kafka.running){              Thread.sleep(1000);          }               } } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;如果不加volatile修饰 ，则有可能一直不会关闭，拿到的running状态 一直是true&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;有序性&lt;/h3&gt; &lt;p&gt;前面的案例使用volatile 优化后&lt;/p&gt; &lt;pre&gt;&lt;code&gt;volatile boolean flag = false;  //线程1： //准备资源 prepare()  flag = true;  //线程2： while(!flag){    Thread.sleep(1000) }  //基于准备好的资源执行操作 execute() &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;比如这个例子，如果使用 volatile来修饰flag变量，一定可以让prepare() 在flag = true;之前先执行，这就禁止指令重排，因为volatile变量规则要求的是，volatile前面的代码一定不能指令重排到volatile变量操作后面，volatile后面的代码也不能指令重排到volatile前面&lt;/p&gt; &lt;p&gt;也可以通过 synchronized 来保证有序性，它保证每个时刻只有一个线程执行同步代码，相当于是让线程顺序执行同步代码。&lt;/p&gt; &lt;h3&gt;原子性&lt;/h3&gt; &lt;p&gt;volatile 不能保证原子性，在有些情况下，可以有限的保证原子性，它主要不是用来保证原子性的！ 比如oracle 64位的long 数字进行操作的时候&lt;/p&gt; &lt;p&gt;保证原子性还是需要synchronized，lock  进行加锁&lt;/p&gt; &lt;h3&gt;原理&lt;/h3&gt; &lt;p&gt;volatile 关键字通过添加内存屏障的方式来禁止指令重排，即重排序时不能把后面的指令放到内存屏障之前。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;lock指令（保证可见性）&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对 volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了&lt;/p&gt; &lt;ul&gt; &lt;li&gt;内存屏障：禁止重排序（保证有序性）&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;Load1: int localVar = this.variable; Load2: int localVar = this.variable2; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Loadload屏障:Load1; LoadLoad;Load2,确保Load1数据的装载先于Load2后所有装载指令,他的意思,Load1对应的代码和Load2对应的代码,是不能指令重排的&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Store1: this.variable=1; StoreStore屏障 Store2: this.variable2=2; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;StoreStore屏障: Store1; StoreStore; Store2,确保 Store1的数据一定刷回主存,对其他cpu 可见,先于 Store2以及后续指令&lt;/p&gt; &lt;p&gt;LoadStore屏障:Load1; LoadStore; Store2,确保Load1指令的数据装载,先于 Store2以及 后续指令&lt;/p&gt; &lt;p&gt;Storeload屏障: Store1; Storeload;Load2,确保 Store1指令的数据一定刷回主存,对其他 cpu可见,先于Load2以及后续指令的数据装载&lt;/p&gt; &lt;h3&gt;作用&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;volatile variable =1 this variable=2=&amp;gt; store操作  int localvariable= this variable=&amp;gt;load操作 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;对于 volatile修改变量的读写操作,都会加入内存屏障,每个 volatile写操作前面,加 Store Store屏障,禁止上面的普通写和他重排;每个 volatile写操作后面,加 Storeload屏障,禁止跟下面的 volatile读/写重排←&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 30 Aug 2021 17:29:00 GMT</pubDate>
    </item>
    <item>
      <title>Seata--分布式事务</title>
      <link>https://maruifu.cn/article/205</link>
      <content:encoded>&lt;h2&gt;&lt;strong&gt;分布式事务基础&lt;/strong&gt;&lt;/h2&gt; &lt;h3&gt;&lt;strong&gt;事务&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;事务指的就是一个操作单元，在这个操作单元中的所有操作最终要保持一致的行为，要么所有操作都成功，要么所有的操作都被撤销。简单地说，事务提供一种“要么什么都不做，要么做全套”机制。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;本地事务&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;本地事物其实可以认为是数据库提供的事务机制。说到数据库事务就不得不说，数据库事务中的四大特性:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;A:原子性(Atomicity)，一个事务中的所有操作，要么全部完成，要么全部不完成&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;C:一致性(Consistency)，在一个事务执行之前和执行之后数据库都必须处于一致性状态&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;I:隔离性(Isolation)，在并发环境中，当不同的事务同时操作相同的数据时，事务之间互不影响&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;D:持久性(Durability)，指的是只要事务成功结束，它对数据库所做的更新就必须永久的保存下来&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元，该执行单元中 的所有操作要么都成功，要么都失败，只要其中任一操作执行失败，都将导致整个事务的回滚&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;分布式事务&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。&lt;/p&gt; &lt;p&gt;简单的说，就是一次大的操作由不同的小操作组成，这些小的操作分布在不同的服务器上，且属于不同 的应用，分布式事务需要保证这些小操作要么全部成功，要么全部失败。&lt;/p&gt; &lt;p&gt;本质上来说，分布式事务就是为了保证不同数据库的数据一致性。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;分布式事务的场景&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;单体系统访问多个数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;一个服务需要调用多个数据库实例完成数据的增删改操作&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.46.17.png" alt="截屏2021-08-29 下午9.46.17" title="截屏2021-08-29 下午9.46.17" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;多个微服务访问同一个数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;多个服务需要调用一个数据库实例完成数据的增删改操作&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.46.44.png" alt="截屏2021-08-29 下午9.46.44" title="截屏2021-08-29 下午9.46.44" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;多个微服务访问多个数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;多个服务需要调用一个数据库实例完成数据的增删改操作&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.47.14.png" alt="截屏2021-08-29 下午9.47.14" title="截屏2021-08-29 下午9.47.14" /&gt;&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;分布式事务解决方案&lt;/strong&gt;&lt;/h2&gt; &lt;h3&gt;&lt;strong&gt;全局事务&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X/Open Distributed Transaction Processing Reference Model。它规定了要实现分布式事务，需要三种角色:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;AP: Application 应用系统 (微服务)&lt;/li&gt; &lt;li&gt;TM: Transaction Manager 事务管理器 (全局事务管理)&lt;/li&gt; &lt;li&gt;RM: Resource Manager 资源管理器 (数据库)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;整个事务分成两个阶段:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;阶段一: 表决阶段，所有参与者都将本事务执行预提交，并将能否成功的信息反馈发给协调者。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;阶段二: 执行阶段，协调者根据所有参与者的反馈，通知所有参与者，步调一致地执行提交或者回&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;滚。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.48.58.png" alt="截屏2021-08-29 下午9.48.58" title="截屏2021-08-29 下午9.48.58" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;提高了数据一致性的概率，实现成本较低&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;单点问题: 事务协调者宕机&lt;/li&gt; &lt;li&gt;同步阻塞: 延迟了提交时间，加长了资源阻塞时间&lt;/li&gt; &lt;li&gt;数据不一致: 提交第二阶段，依然存在commit结果未知的情况，有可能导致数据不一致&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;可靠消息服务&lt;/h3&gt; &lt;p&gt;基于可靠消息服务的方案是通过消息中间件保证上、下游应用数据操作的一致性。假设有A和B两个系 统，分别可以处理任务A和任务B。此时存在一个业务流程，需要将任务A和任务B在同一个事务中处 理。就可以使用消息中间件来实现这种分布式事务。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.50.40.png" alt="截屏2021-08-29 下午9.50.40" title="截屏2021-08-29 下午9.50.40" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;第一步:消息由系统A投递到中间件&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;在系统A处理任务A前，首先向消息中间件发送一条消息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;消息中间件收到后将该条消息持久化，但并不投递。持久化成功后，向A回复一个确认应答&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;系统A收到确认应答后，则可以开始处理任务A&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;任务A处理完成后，向消息中间件发送Commit或者Rollback请求。该请求发送完成后，对系统A而&lt;/p&gt; &lt;p&gt;言，该事务的处理过程就结束了&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果消息中间件收到Commit，则向B系统投递消息;如果收到Rollback，则直接丢弃消息。但是&lt;/p&gt; &lt;p&gt;如果消息中间件收不到Commit和Rollback指令，那么就要依靠&amp;quot;超时询问机制&amp;quot;。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;超时询问机制&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;系统A除了实现正常的业务流程外，还需提供一个事务询问的接口，供消息中间件调用。当消息中 间件收到发布消息便开始计时，如果到了超时没收到确认指令，就会主动调用系统A提供的事务询 问接口询问该系统目前的状态。该接口会返回三种结果，中间件根据三种结果做出不同反应:&lt;/p&gt; &lt;p&gt;提交:将该消息投递给系统B 回滚:直接将条消息丢弃 处理中:继续等待&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;strong&gt;第二步:消息由中间件投递到系统B&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;消息中间件向下游系统投递完消息后便进入阻塞等待状态，下游系统便立即进行任务的处理，任务处理完成后便向消息中间件返回应答。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;如果消息中间件收到确认应答后便认为该事务处理完毕&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如果消息中间件在等待确认应答超时之后就会重新投递，直到下游消费者返回消费成功响应为止。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;一般消息中间件可以设置消息重试的次数和时间间隔，如果最终还是不能成功投递，则需要手工干预。 这里之所以使用人工干预，而不是使用让A系统回滚，主要是考虑到整个系统设计的复杂度问题。&lt;/p&gt; &lt;p&gt;基于可靠消息服务的分布式事务，前半部分使用异步，注重性能;后半部分使用同步，注重开发成本。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;最大努力通知&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;最大努力通知也被称为定期校对，其实是对第二种解决方案的进一步优化。它引入了本地消息表来记录错误消息，然后加入失败消息的定期校对功能，来进一步保证消息会被下游系统消费。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.54.48.png" alt="截屏2021-08-29 下午9.54.48" title="截屏2021-08-29 下午9.54.48" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;第一步:消息由系统A投递到中间件&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;处理业务的同一事务中，向本地消息表中写入一条记录&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;准备专门的消息发送者不断地发送本地消息表中的消息到消息中间件，如果发送失败则重试&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;第二步:消息由中间件投递到系统B&lt;/strong&gt;&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt; &lt;p&gt;消息中间件收到消息后负责将该消息同步投递给相应的下游系统，并触发下游系统的任务执行&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;当下游系统处理成功后，向消息中间件反馈确认应答，消息中间件便可以将该条消息删除，从而该 事务完成&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;对于投递失败的消息，利用重试机制进行重试，对于重试失败的，写入错误消息表&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;消息中间件需要提供失败消息的查询接口，下游系统会定期查询失败消息，并将其消费&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;这种方式的优缺点:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;优点: 一种非常经典的实现，实现了最终一致性。&lt;/p&gt; &lt;p&gt;缺点: 消息表会耦合到业务系统中，如果没有封装好的解决方案，会有很多杂活需要处理。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;TCC事务&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;TCC即为Try Confifirm Cancel，它属于补偿型分布式事务。TCC实现分布式事务一共有三个步骤:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; 尝试待执行的业务:这个过程并未执行业务，只是完成所有业务的一致性检查，并预留好执 行所需的全部资源&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Confifirm:&lt;/strong&gt; 确认执行业务:确认执行业务操作，不做任何业务检查， 只使用Try阶段预留的业务 资源。通常情况下，采用TCC则认为 Confifirm阶段是不会出错的。即:只要Try成功，Confifirm 一定成功。若Confifirm阶段真的出错了，需引入重试机制或人工处理。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Cancel:&lt;/strong&gt; 取消待执行的业务:取消Try阶段预留的业务资源。通常情况下，采用TCC则认为Cancel 阶段也是一定成功的。若Cancel阶段真的出错了，需引入重试机制或人工处理&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.59.42.png" alt="截屏2021-08-29 下午9.59.42" title="截屏2021-08-29 下午9.59.42" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-9.59.51.png" alt="截屏2021-08-29 下午9.59.51" title="截屏2021-08-29 下午9.59.51" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;TCC两阶段提交与XA两阶段提交的区别是:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;XA是资源层面的分布式事务，强一致性，在两阶段提交的整个过程中，一直会持有资源的锁。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;TCC是业务层面的分布式事务，最终一致性，不会一直持有资源的锁。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;TCC事务的优缺点:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;:把数据库层的二阶段提交上提到了应用层来实现，规避了数据库层的2PC性能低下问题。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;:TCC的Try、Confifirm和Cancel操作功能需业务提供，开发成本高。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;&lt;strong&gt;Seata介绍&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;2019 年 1 月，阿里巴巴中间件团队发起了开源项目 &lt;strong&gt;Fescar&lt;/strong&gt;(Fast &amp;amp; EaSy Commit AndRollback)， 其愿景是让分布式事务的使用像本地事务的使用一样，简单和高效，并逐步解决开发者们遇到的分布式 事务方面的所有难题。后来更名为 &lt;strong&gt;Seata&lt;/strong&gt;，意为:Simple Extensible Autonomous Transaction Architecture，是一套分布式事务解决方案。&lt;/p&gt; &lt;p&gt;Seata的设计目标是对业务无侵入，因此从业务无侵入的2PC方案着手，在传统2PC的基础上演进。它把 一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事 务达成一致，要么一起成功提交，要么一起失败回滚。此外，通常分支事务本身就是一个关系数据库的 本地事务。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-10.05.03.png" alt="截屏2021-08-29 下午10.05.03" title="截屏2021-08-29 下午10.05.03" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Seata主要由三个重要组件组成:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;TC:Transaction Coordinator 事务协调器，管理全局的分支事务的状态，用于全局性事务的提交 和回滚。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;TM:Transaction Manager 事务管理器，用于开启、提交或者回滚全局事务。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;RM:Resource Manager 资源管理器，用于分支事务上的资源管理，向TC注册分支事务，上报分 支事务的状态，接受TC的命令来提交或者回滚分支事务。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-10.06.00.png" alt="截屏2021-08-29 下午10.06.00" title="截屏2021-08-29 下午10.06.00" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Seata&lt;strong&gt;&lt;strong&gt;的执行流程如下&lt;/strong&gt;&lt;/strong&gt;:&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;A服务的TM向TC申请开启一个全局事务，TC就会创建一个全局事务并返回一个唯一的XID&lt;/li&gt; &lt;li&gt;A服务的RM向TC注册分支事务，并及其纳入XID对应全局事务的管辖&lt;/li&gt; &lt;li&gt;A服务执行分支事务，向数据库做操作&lt;/li&gt; &lt;li&gt;A服务开始远程调用B服务，此时XID会在微服务的调用链上传播&lt;/li&gt; &lt;li&gt;B服务的RM向TC注册分支事务，并将其纳入XID对应的全局事务的管辖&lt;/li&gt; &lt;li&gt;B服务执行分支事务，向数据库做操作&lt;/li&gt; &lt;li&gt;全局事务调用链处理完毕，TM根据有无异常向TC发起全局事务的提交或者回滚&lt;/li&gt; &lt;li&gt;TC协调其管辖之下的所有分支事务， 决定是否回滚&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;Seata实现2PC与传统2PC的差别:&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;架构层次方面，传统2PC方案的 RM 实际上是在数据库层，RM本质上就是数据库自身，通过XA协 议实现，而 Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。&lt;/li&gt; &lt;li&gt;两阶段提交方面，传统2PC无论第二阶段的决议是commit还是rollback，事务性资源的锁都要保 持到Phase2完成才释放。而Seata的做法是在Phase1 就将本地事务提交，这样就可以省去Phase2 持锁的时间，整体提高效率&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;&lt;strong&gt;Seata实现分布式事务控制&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;本示例通过Seata中间件实现分布式事务，模拟电商中的下单和扣库存的过程&lt;/p&gt; &lt;p&gt;我们通过订单微服务执行下单操作，然后由订单微服务调用商品微服务扣除库存&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-10.08.39.png" alt="截屏2021-08-29 下午10.08.39" title="截屏2021-08-29 下午10.08.39" /&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;案例基本代码&lt;/strong&gt;&lt;/h3&gt; &lt;h4&gt;&lt;strong&gt;修改order微服务&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;controller&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@RestController @Slf4j public class OrderController5 {   @Autowired   private OrderServiceImpl5 orderService;   //下单   @RequestMapping(&amp;quot;/order/prod/{pid}&amp;quot;)   public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {     log.info(&amp;quot;接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息&amp;quot;, pid);     return orderService.createOrder(pid);    } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;OrderService&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Service @Slf4j public class OrderServiceImpl5{      @Autowired     private OrderDao orderDao;     @Autowired     private ProductService productService;     @Autowired     private RocketMQTemplate rocketMQTemplate;     @GlobalTransactional     public Order createOrder(Integer pid) {             //1 调用商品微服务,查询商品信息             Product product = productService.findByPid(pid);              log.info(&amp;quot;查询到{}号商品的信息,内容是:{}&amp;quot;, pid, JSON.toJSONString(product));              //2 下单(创建订单)             Order order = new Order();             order.setUid(1);             order.setUsername(&amp;quot;测试用户&amp;quot;);             order.setPid(pid);             order.setPname(product.getPname());              order.setPprice(product.getPprice());             order.setNumber(1);             orderDao.save(order);             log.info(&amp;quot;创建订单成功,订单信息为{}&amp;quot;, JSON.toJSONString(order));             //3 扣库存             productService.reduceInventory(pid, order.getNumber());             //4 向mq中投递一个下单成功的消息              rocketMQTemplate.convertAndSend(&amp;quot;order-topic&amp;quot;, order);             return order;        } }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;ProductService&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@FeignClient(value = &amp;quot;service-product&amp;quot;)  public interface ProductService {   //减库存    @RequestMapping(&amp;quot;/product/reduceInventory&amp;quot;)   void reduceInventory(@RequestParam(&amp;quot;pid&amp;quot;) Integer pid,@RequestParam(&amp;quot;num&amp;quot;) } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;修改Product微服务&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;controller&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//减少库存  @RequestMapping(&amp;quot;/product/reduceInventory&amp;quot;) public void reduceInventory(Integer pid, int num) {   productService.reduceInventory(pid, num);  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;service&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  @Override public void reduceInventory(Integer pid, int num) {    Product product = productDao.findById(pid).get();    product.setStock(product.getStock() - num);    //减库存   productDao.save(product);  } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;异常模拟&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;在ProductServiceImpl的代码中模拟一个异常, 然后调用下单接口&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Override public void reduceInventory(Integer pid, Integer number) {    Product product = productDao.findById(pid).get();   if (product.getStock() &amp;lt; number) {     throw new RuntimeException(&amp;quot;库存不足&amp;quot;);    }   int i = 1 / 0;    product.setStock(product.getStock() - number);    productDao.save(product); } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;启动Seata&lt;/strong&gt;&lt;/h3&gt; &lt;h4&gt;&lt;strong&gt;下载seata&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;下载地址:https://github.com/seata/seata/releases/v0.9.0/&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;修改配置文件&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;将下载得到的压缩包进行解压，进入conf目录，调整下面的配置文件:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;registry.conf&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;registry {   # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa   type = &amp;quot;nacos&amp;quot;    nacos {     serverAddr = &amp;quot;localhost&amp;quot;     namespace = &amp;quot;&amp;quot;     cluster = &amp;quot;default&amp;quot;   } } config {   # file、nacos 、apollo、zk、consul、etcd3   type = &amp;quot;nacos&amp;quot;   nacos {     serverAddr = &amp;quot;localhost&amp;quot;     namespace = &amp;quot;&amp;quot;   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;nacos-confifig.txt&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt; service.vgroup_mapping.service-product=default   service.vgroup_mapping.service-order=default &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;初始化seata在nacos的配置&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 初始化seata 的nacos配置 # 注意: 这里要保证nacos是已经正常运行的  cd conf nacos-config.sh 127.0.0.1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行成功后可以打开Nacos的控制台，在配置列表中，可以看到初始化了很多Group为SEATA_GROUP 的配置。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;启动seata服务&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;cd bin seata-server.bat -p 9000 -m file &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr 的服务。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;使用Seata实现事务控制&lt;/strong&gt;&lt;/h3&gt; &lt;h4&gt;&lt;strong&gt;初始化数据表&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;在我们的数据库中加入一张undo_log表,这是Seata记录事务日志要用到的表&lt;/p&gt; &lt;pre&gt;&lt;code&gt;CREATE TABLE `undo_log`(   `id` BIGiNT(20) NOT NULL AUTO_INCREMENT,    `branch_id` BIGiNT(20) NOT NULL,   `xid` VARcHAR(100) NOT NULL,   `context` VARcHAR(128) NOT NULL,    `rollback_info` LONGBLOB NOT NULL,    `log_status` iNT(11) NOT NULL,   `log_created` DATETIME NOT NULL,    `log_modified` DATETIME NOT NULL,   `ext` VARcHAR(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)  ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;添加配置&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;在需要进行分布式控制的微服务中进行下面几项配置:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;添加依赖&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-seata&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-config&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;DataSourceProxyConfifig&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Seata 是通过代理数据源实现事务分支的，所以需要配置 io.seata.rm.datasource.DataSourceProxy 的 Bean，且是 @Primary默认的数据源，否则事务不会回滚，无法实现分布式事务&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Configuration public class DataSourceProxyConfig {     @Bean     @ConfigurationProperties(prefix = &amp;quot;spring.datasource&amp;quot;) public DruidDataSource druidDataSource() {         return new DruidDataSource();     }     @Primary     @Bean     public DataSourceProxy dataSource(DruidDataSource druidDataSource) {         return new DataSourceProxy(druidDataSource);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;registry.conf&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在resources下添加Seata的配置文件 registry.conf&lt;/p&gt; &lt;pre&gt;&lt;code&gt;registry {     type = &amp;quot;nacos&amp;quot;     nacos {       serverAddr = &amp;quot;localhost&amp;quot;       namespace = &amp;quot;public&amp;quot;       cluster = &amp;quot;default&amp;quot;     } } config {     type = &amp;quot;nacos&amp;quot;     nacos {       serverAddr = &amp;quot;localhost&amp;quot;       namespace = &amp;quot;public&amp;quot;       cluster = &amp;quot;default&amp;quot;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;bootstrap.yaml&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:  application:   name: service-product  cloud:   nacos:    config:     server-addr: localhost:8848 # nacos的服务端地址      namespace: public       group: SEATA_GROUP   alibaba:    seata:     tx-service-group: ${spring.application.name } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;在order微服务开启全局事务&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;@GlobalTransactional//全局事务控制 public Order createOrder(Integer pid) {} &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;测试&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;再次下单测试&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;seata运行流程分析&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-10.32.57.png" alt="截屏2021-08-29 下午10.32.57" title="截屏2021-08-29 下午10.32.57" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;要点说明:&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;每个RM使用DataSourceProxy连接数据库，其目的是使用ConnectionProxy，使用数据源和数据 连接代理的目的就是在第一阶段将undo_log和业务数据放在一个本地事务提交，这样就保存了只 要有业务操作就一定有undo_log。&lt;/li&gt; &lt;li&gt;在第一阶段undo_log中存放了数据修改前和修改后的值，为事务回滚作好准备，所以第一阶段完 成就已经将分支事务提交，也就释放了锁资源。&lt;/li&gt; &lt;li&gt;TM开启全局事务开始，将XID全局事务id放在事务上下文中，通过feign调用也将XID传入下游分支 事务，每个分支事务将自己的Branch ID分支事务ID与XID关联。&lt;/li&gt; &lt;li&gt;第二阶段全局事务提交，TC会通知各各分支参与者提交分支事务，在第一阶段就已经提交了分支 事务，这里各各参与者只需要删除undo_log即可，并且可以异步执行，第二阶段很快可以完成。&lt;/li&gt; &lt;li&gt;第二阶段全局事务回滚，TC会通知各各分支参与者回滚分支事务，通过 XID 和 Branch ID 找到相 应的回滚日志，通过回滚日志生成反向的 SQL 并执行，以完成分支事务回滚到之前的状态，如果 回滚失败则会重试回滚操作&lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Sun, 29 Aug 2021 14:39:00 GMT</pubDate>
    </item>
    <item>
      <title>Nacos Config--服务配置</title>
      <link>https://maruifu.cn/article/204</link>
      <content:encoded>&lt;h2&gt;服务配置中心介绍&lt;/h2&gt; &lt;p&gt;首先我们来看一下,微服务架构下关于配置文件的一些问题:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;配置文件相对分散。在一个微服务架构下，配置文件会随着微服务的增多变的越来越多，而且分散 在各个微服务中，不好统一配置和管理。&lt;/li&gt; &lt;li&gt;配置文件无法区分环境。微服务项目可能会有多个环境，例如:测试环境、预发布环境、生产环 境。每一个环境所使用的配置理论上都是不同的，一旦需要修改，就需要我们去各个微服务下手动 维护，这比较困难。&lt;/li&gt; &lt;li&gt;配置文件无法实时更新。我们修改了配置文件之后，必须重新启动微服务才能使配置生效，这对一 个正在运行的项目来说是非常不友好的。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;基于上面这些问题，我们就需要&lt;strong&gt;配置中心&lt;/strong&gt;的加入来解决这些问题。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置中心的思路是:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;首先把项目中各种配置全部都放到一个集中的地方进行统一管理，并提供一套标准的接口。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;当各个服务需要获取配置的时候，就来配置中心的接口拉取自己的配置。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;当配置中心中的各种参数有更新的时候，也能通知到各个服务实时的过来同步最新的信息，使之动态更新。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;当加入了服务配置中心之后，我们的系统架构图会变成下面这样&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.23.36.png" alt="截屏2021-08-29 下午4.23.36" title="截屏2021-08-29 下午4.23.36" /&gt;&lt;/p&gt; &lt;p&gt;在业界常见的服务配置中心，有下面这些:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Apollo&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Apollo是由携程开源的分布式配置中心。特点有很多，比如:配置更新之后可以实时生效，支持灰度发 布功能，并且能对所有的配置进行版本管理、操作审计等功能，提供开放平台API。并且资料也写的很 详细。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Disconf&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Disconf是由百度开源的分布式配置中心。它是基于Zookeeper来实现配置变更后实时通知和生效的。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;SpringCloud Confifig&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这是Spring Cloud中带的配置中心组件。它和Spring是无缝集成，使用起来非常方便，并且它的配置存 储支持Git。不过它没有可视化的操作界面，配置的生效也不是实时的，需要重启或去刷新。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Nacos&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这是SpingCloud alibaba技术栈中的一个组件，前面我们已经使用它做过服务注册中心。其实它也集成 了服务配置的功能，我们可以直接使用它作为服务配置中心。&lt;/p&gt; &lt;h2&gt;Nacos Config 入门&lt;/h2&gt; &lt;p&gt;使用nacos作为配置中心，其实就是将nacos当做一个服务端，将各个微服务看成是客户端，我们将各个微服务的配置文件统一存放在nacos上，然后各个微服务从nacos上拉取配置即可。&lt;/p&gt; &lt;p&gt;接下来我们以商品微服务为例，学习nacos confifig的使用。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;搭建nacos环境【使用现有的nacos环境即可】&lt;/li&gt; &lt;li&gt;在微服务中引入nacos的依赖&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-config&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;在微服务中添加nacos confifig的配置&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;注意:不能使用原来的application.yml作为配置文件，而是新建一个bootstrap.yml作为配置文件&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;配置文件优先级(由高到低):&lt;/p&gt; &lt;p&gt;bootstrap.properties -&amp;gt; bootstrap.yml -&amp;gt; application.properties -&amp;gt; application.yml&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:   application:     name: service-product   cloud:     nacos:       config:         server-addr: 127.0.0.1:8848 #nacos中心地址         file-extension: yaml # 配置文件格式 profiles:   profiles:     active: dev # 环境标识 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="4"&gt; &lt;li&gt;在nacos中添加配置&lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;点击配置列表，点击右边+号，新建配置。在新建配置过程中，要注意下面的细节:&lt;/p&gt; &lt;p&gt;1)Data ID不能随便写，要跟配置文件中的对应，对应关系如图所示&lt;/p&gt; &lt;p&gt;2)配置文件格式要跟配置文件的格式对应，且目前仅仅支持YAML和Properties&lt;/p&gt; &lt;p&gt;3)配置内容按照上面选定的格式书写&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.30.26.png" alt="截屏2021-08-29 下午4.30.26" title="截屏2021-08-29 下午4.30.26" /&gt;&lt;/p&gt; &lt;ol start="5"&gt; &lt;li&gt;注释本地的application.yam中的内容， 启动程序进行测试&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;如果依旧可以成功访问程序，说明我们nacos的配置中心功能已经实现&lt;/p&gt; &lt;h2&gt;Nacos Config 深入&lt;/h2&gt; &lt;h3&gt;&lt;strong&gt;配置动态刷新&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;在入门案例中，我们实现了配置的远程存放，但是此时如果修改了配置，我们的程序是无法读取到的， 因此，我们需要开启配置的动态刷新功能。&lt;/p&gt; &lt;p&gt;在nacos中的service-product-dev.yaml配置项中添加下面配置:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;config:   appName: product &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;方式一:硬编码方式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@RestController public class NacosConfigController { @Autowired private ConfigurableApplicationContext applicationContext;      @GetMapping( &amp;quot;/nacos-config-test1&amp;quot; )     public String nacosConfingTest1(){      return (applicationContext.getEnvironment().getProperty(&amp;quot;config.appName&amp;quot;));     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;方式二:注解方式(推荐)&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@RestController @RefreshScope /* 只需要在需要动态读取配置的类上添加此注解就可以 */ public class NacosConfigController {   @Value(&amp;quot;${config.appName}&amp;quot;) private String appName;       @GetMapping(&amp;quot;/nacos-config-test2&amp;quot;)     public String nacosConfingTest2(){         return(appName);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;配置共享&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;当配置越来越多的时候，我们就发现有很多配置是重复的，这时候就考虑可不可以将公共配置文件提取出来，然后实现共享呢?当然是可以的。接下来我们就来探讨如何实现这一功能。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;同一个微服务的不同环境之间共享配置&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如果想在同一个微服务的不同环境之间实现配置共享，其实很简单。只需要提取一个以 spring.application.name 命名的配置文件，然后将其所有环境的公共配置放在里面即可。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;新建一个名为service-product.yaml配置存放商品微服务的公共配置&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.35.11.png" alt="截屏2021-08-29 下午4.35.11" title="截屏2021-08-29 下午4.35.11" /&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;新建一个名为service-product-test.yaml配置存放测试环境的配置&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.35.36.png" alt="截屏2021-08-29 下午4.35.36" title="截屏2021-08-29 下午4.35.36" /&gt;&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt;新建一个名为service-productr-dev.yaml配置存放开发环境的配置&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.36.33.png" alt="截屏2021-08-29 下午4.36.33" title="截屏2021-08-29 下午4.36.33" /&gt;&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;添加测试方法&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;@RestController @RefreshScope public class NacosConfigController {   @Value( &amp;quot;${config.env}&amp;quot; ) private String env;  /* 3 同一微服务的不同环境下共享配置 */ @GetMapping( &amp;quot;/nacos-config-test3&amp;quot; )     public String nacosConfingTest3(){         return(env);     } } &lt;/code&gt;&lt;/pre&gt; &lt;ol start="5"&gt; &lt;li&gt;访问测试&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.38.26.png" alt="截屏2021-08-29 下午4.38.26" title="截屏2021-08-29 下午4.38.26" /&gt;&lt;/p&gt; &lt;ol start="6"&gt; &lt;li&gt;接下来，修改bootstrap.yml中的配置，将active设置成test，再次访问，观察结果&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;spring:   profiles:     active: test # 环境标识 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;不同微服务中间共享配置&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;不同为服务之间实现配置共享的原理类似于文件引入，就是定义一个公共配置，然后在当前配置中引 入。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;在nacos中定义一个DataID为all-service.yaml的配置，用于所有微服务共享&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;spring:   datasource:     driver-class-name: com.mysql.jdbc.Driver     url: jdbc:mysql://shop?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true      username: root     password: root jpa:   properties:     hibernate:       hbm2ddl:         auto: update   dialect: org.hibernate.dialect.MySQL5InnoDBDialect   cloud:     nacos:       discovery:        server-addr: 127.0.0.1:8848 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="2"&gt; &lt;li&gt;在nacos的中修改service-product.yaml中为下面内容&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;server:   port: 8081 config:   appName: product &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;修改bootstrap.yaml&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;spring:   application:    name: service-product   profiles:     active: dev # 环境标识   cloud:     nacos:       config:         server-addr: 127.0.0.1:8848 #nacos中心地址         file-extension: yaml # 配置文件格式         shared-dataids: all-service.yaml # 配置要引入的配置          refreshable-dataids: all-service.yaml # 配置要实现动态配置刷新的配置  &lt;/code&gt;&lt;/pre&gt; &lt;ol start="4"&gt; &lt;li&gt;启动商品微服务进行测试&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;Nacos 的几个概念&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;命名空间(Namespace)&lt;/strong&gt; 命名空间可用于进行不同环境的配置隔离。一般一个环境划分到一个命名空间&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置分组(Group)&lt;/strong&gt; 配置分组用于将不同的服务可以归类到同一分组。一般将一个项目的配置分到一组&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置集(Data ID)&lt;/strong&gt; 在系统中，一个配置文件通常就是一个配置集。一般微服务的配置就是一个配置集&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-4.46.16.png" alt="截屏2021-08-29 下午4.46.16" title="截屏2021-08-29 下午4.46.16" /&gt;&lt;/p&gt; &lt;p&gt;结果如下&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/23/image-20220223160522368.png" alt="image-20220223160522368" title="image-20220223160522368" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# all-service.yaml   spring:   zipkin:     #开启zipkin分析     enabled: true     #让nacos把它当成一个URL，而不要当做服务名     discoveryClientEnabled: false   sleuth:     sampler:       #限速器，每秒采集10个请求，防止大并发过载。推荐       #rate: 10       #采集率，大并发可能采集率数量也会很高。采样的百分比       probability: 0.1   application:     name: service-product   datasource:     driver-class-name: com.mysql.cj.jdbc.Driver    jpa:     database-platform: org.hibernate.dialect.MySQL5InnoDBDialect     show-sql: true     hibernate:       ddl-auto: update       use-new-id-generator-mappings: false  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# service-product.yaml  config:   appName: product &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# service-product-dev.yaml  config:   env: dev  server:   port: 8081 spring:   zipkin:     #zipkin服务地址     baseUrl: http://127.0.0.1:9411/   datasource:     url: jdbc:mysql://127.0.0.1:3306/spring-cloud?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: Mrf12345   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848 &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# service-product-test.yaml  config:   env: test  server:   port: 8081 spring:   zipkin:     #zipkin服务地址     baseUrl: http://127.0.0.1:9411/   datasource:     url: jdbc:mysql://127.0.0.1:3306/spring-cloud?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: Mrf12345   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 29 Aug 2021 08:46:00 GMT</pubDate>
    </item>
    <item>
      <title>SMS--短信服务</title>
      <link>https://maruifu.cn/article/203</link>
      <content:encoded>&lt;h2&gt;短信服务介绍&lt;/h2&gt; &lt;p&gt;短信服务（Short Message Service）是阿里云为用户提供的一种通信服务的能力。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;产品优势：覆盖全面、高并发处理、消息堆积处理、开发管理简单、智能监控调度&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;产品功能：短信通知、短信验证码、推广短信、异步通知、数据统计&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;应用场景：短信验证码、系统信息推送、推广短信等&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.03.56.png" alt="截屏2021-08-29 下午1.03.56" title="截屏2021-08-29 下午1.03.56" /&gt;&lt;/p&gt; &lt;h2&gt;短信服务使用&lt;/h2&gt; &lt;p&gt;接下来,我们使用短信验证码功能来演示短信服务的使用。流程如下:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.04.52.png" alt="截屏2021-08-29 下午1.04.52" title="截屏2021-08-29 下午1.04.52" /&gt;&lt;/p&gt; &lt;h3&gt;准备工作&lt;/h3&gt; &lt;h4&gt;实名认证&lt;/h4&gt; &lt;p&gt;https://help.aliyun.com/document_detail/48263.html?spm=5176.11533457.J_1089570.9.15da5333ZUkUdR&lt;/p&gt; &lt;h4&gt;开通短信服务&lt;/h4&gt; &lt;p&gt;https://www.aliyun.com/product/sms?spm=5176.11533457.J_1089570.9.15da5333ZUkUdR&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.12.51.png" alt="截屏2021-08-29 下午1.12.51" title="截屏2021-08-29 下午1.12.51" /&gt;&lt;/p&gt; &lt;h4&gt;申请认证秘钥&lt;/h4&gt; &lt;p&gt;https://ram.console.aliyun.com/manage/ak&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.29.37.png" alt="截屏2021-08-29 下午1.29.37" title="截屏2021-08-29 下午1.29.37" /&gt;&lt;/p&gt; &lt;h4&gt;申请短信签名&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.32.48.png" alt="截屏2021-08-29 下午1.32.48" title="截屏2021-08-29 下午1.32.48" /&gt;&lt;/p&gt; &lt;h4&gt;申请短信模板&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/29/2021-08-29-1.33.03.png" alt="截屏2021-08-29 下午1.33.03" title="截屏2021-08-29 下午1.33.03" /&gt;&lt;/p&gt; &lt;h3&gt;短信服务 API 介绍&lt;/h3&gt; &lt;h4&gt;短信发送&lt;/h4&gt; &lt;p&gt;调用SendSms发送短信。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;请求参数&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;&lt;strong&gt;名称&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;**类型 **&lt;/th&gt;&lt;th&gt;是否必选&lt;/th&gt;&lt;th&gt;**示例值 **&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;PhoneNumbers&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;15900000000&lt;/td&gt;&lt;td&gt;接收短信的手机号码。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;SignName&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;阿里云&lt;/td&gt;&lt;td&gt;短信签名名称。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;TemplateCode&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;SMS_153055065&lt;/td&gt;&lt;td&gt;短信模板ID。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;TemplateParam&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;td&gt;{&amp;quot;code&amp;quot;:&amp;quot;1111&amp;quot;}&lt;/td&gt;&lt;td&gt;短信模板变量的值，JSON格式&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;strong&gt;返回数据&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;&lt;strong&gt;示例值&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;描述&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;BizId&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;900619746936498440&lt;/td&gt;&lt;td&gt;发送回执ID，可根据它查询具体的发 送&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Code&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;td&gt;请求状态码。返回OK代表请求成功。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Message&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;OK&lt;/td&gt;&lt;td&gt;状态码的描述。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;RequestId&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;F655A8D5-B967-440B- 8683&lt;/td&gt;&lt;td&gt;请求ID。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4&gt;短信查询&lt;/h4&gt; &lt;p&gt;调用QuerySendDetails接口查看短信发送记录和发送状态。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;请求参数&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;是否必选&lt;/th&gt;&lt;th&gt;示例值&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;CurrentPage&lt;/td&gt;&lt;td&gt;Long&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;分页查看发送记录，指定发送记录的当前页码。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;PageSize&lt;/td&gt;&lt;td&gt;Long&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;分页查看发送记录，指定每页显示的短信记录数量。取值范围为1~50。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;PhoneNumber&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;1590000****&lt;/td&gt;&lt;td&gt;接收短信的手机号码。格式：国内短信：11位手机号码，例如1590000****。国际/港澳台消息：国际区号+号码，例如8520000****。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;SendDate&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;20181228&lt;/td&gt;&lt;td&gt;短信发送日期，支持查询最近30天的记录。格式为yyyyMMdd，例如20181225。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;BizId&lt;/td&gt;&lt;td&gt;String&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;td&gt;134523^4351232&lt;/td&gt;&lt;td&gt;发送回执ID，即发送流水号。调用发送接口SendSms或SendBatchSms发送短信时，返回值中的BizId字段。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;strong&gt;返回数据&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;名称&lt;/th&gt;&lt;th align="left"&gt;类型&lt;/th&gt;&lt;th align="left"&gt;示例值&lt;/th&gt;&lt;th align="left"&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;Code&lt;/td&gt;&lt;td align="left"&gt;String&lt;/td&gt;&lt;td align="left"&gt;OK&lt;/td&gt;&lt;td align="left"&gt;请求状态码。返回OK代表请求成功。其他错误码，请参见&lt;a href="https://help.aliyun.com/document_detail/101346.htm" target="_blank"&gt;错误码列表&lt;/a&gt;。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Message&lt;/td&gt;&lt;td align="left"&gt;String&lt;/td&gt;&lt;td align="left"&gt;OK&lt;/td&gt;&lt;td align="left"&gt;状态码的描述。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;RequestId&lt;/td&gt;&lt;td align="left"&gt;String&lt;/td&gt;&lt;td align="left"&gt;819BE656-D2E0-4858-8B21-B2E477085AAF&lt;/td&gt;&lt;td align="left"&gt;请求ID。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;SmsSendDetailDTOs&lt;/td&gt;&lt;td align="left"&gt;Array of SmsSendDetailDTO&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;短信发送明细。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;TotalCount&lt;/td&gt;&lt;td align="left"&gt;String&lt;/td&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;短信发送总条数。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4&gt;功能测试&lt;/h4&gt; &lt;p&gt;第1步: 引入阿里云服务依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!--短信发送--&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-alicloud-sms&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 使用阿里云提供的Demo测试短信发送&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest; import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile;  import java.text.SimpleDateFormat; import java.util.Date;  public class SmsDemo {     //产品名称:云通信短信API产品,开发者无需替换     static final String product = &amp;quot;Dysmsapi&amp;quot;; //产品域名,开发者无需替换     static final String domain = &amp;quot;dysmsapi.aliyuncs.com&amp;quot;;     // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)     static final String accessKeyId = &amp;quot;yourAccessKeyId&amp;quot;;     static final String accessKeySecret = &amp;quot;yourAccessKeySecret&amp;quot;; //短信发送      public static SendSmsResponse sendSms() throws ClientException {          //可自助调整超时时间         System.setProperty(&amp;quot;sun.net.client.defaultConnectTimeout&amp;quot;, &amp;quot;10000&amp;quot;);         System.setProperty(&amp;quot;sun.net.client.defaultReadTimeout&amp;quot;, &amp;quot;10000&amp;quot;);          //初始化acsClient,暂不支持region化         IClientProfile profile = DefaultProfile.getProfile(&amp;quot;cn-hangzhou&amp;quot;,accessKeyId, accessKeySecret);                  DefaultProfile.addEndpoint(&amp;quot;cn-hangzhou&amp;quot;, &amp;quot;cn-hangzhou&amp;quot;, product,                 domain);         IAcsClient acsClient = new DefaultAcsClient(profile);         //组装请求对象-具体描述见控制台-文档部分内容         SendSmsRequest request = new SendSmsRequest();         //必填:待发送手机号         request.setPhoneNumbers(&amp;quot;15000000000&amp;quot;);         //必填:短信签名-可在短信控制台中找到         request.setSignName(&amp;quot;中古盲盒&amp;quot;);         //必填:短信模板-可在短信控制台中找到          request.setTemplateCode(&amp;quot;SMS_1000000&amp;quot;);         // 可选:模板中的变量替换JSON串,如模板内容为&amp;quot;亲爱的${name},您的验证码为${code}&amp;quot;时,此处的值为         request.setTemplateParam(&amp;quot;{\&amp;quot;name\&amp;quot;:\&amp;quot;Tom\&amp;quot;, \&amp;quot;code\&amp;quot;:\&amp;quot;123\&amp;quot;}&amp;quot;);         /**          *  选填-上行短信扩展码(无特殊需求用户请忽略此字段)          *   request.setSmsUpExtendCode(&amp;quot;90997&amp;quot;)          */          // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者          request.setOutId(&amp;quot;yourOutId&amp;quot;);         // hint 此处可能会抛出异常,注意catch         SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);         return sendSmsResponse;     }      //短信查询     public static QuerySendDetailsResponse querySendDetails(String bizId) throws ClientException {          //可自助调整超时时间         System.setProperty(&amp;quot;sun.net.client.defaultReadTimeout&amp;quot;, &amp;quot;10000&amp;quot;);         System.setProperty(&amp;quot;sun.net.client.defaultConnectTimeout&amp;quot;, &amp;quot;10000&amp;quot;);         //初始化acsClient,暂不支持region化         IClientProfile profile = DefaultProfile.getProfile(&amp;quot;cn-hangzhou&amp;quot;,accessKeyId, accessKeySecret);         DefaultProfile.addEndpoint(&amp;quot;cn-hangzhou&amp;quot;, &amp;quot;cn-hangzhou&amp;quot;, product,domain);         IAcsClient acsClient = new DefaultAcsClient(profile); //组装请求对象         QuerySendDetailsRequest request = new QuerySendDetailsRequest(); //必填-号码         request.setPhoneNumber(&amp;quot;15000000000&amp;quot;);         //可选-流水号         request.setBizId(bizId);         //必填-发送日期 支持30天内记录查询,格式yyyyMMdd         SimpleDateFormat ft = new SimpleDateFormat(&amp;quot;yyyyMMdd&amp;quot;);         request.setSendDate(ft.format(new Date()));         //必填-页大小         request.setPageSize(10L);         //必填-当前页码从1开始计数         request.setCurrentPage(1L);         //hint 此处可能会抛出异常,注意catch         QuerySendDetailsResponse querySendDetailsResponse =acsClient.getAcsResponse(request);         return querySendDetailsResponse;     }      public static void main(String[] args) throws ClientException, InterruptedException {          //发短信         SendSmsResponse response = sendSms();         System.out.println(&amp;quot;短信接口返回的数据----------------&amp;quot;);         System.out.println(&amp;quot;Code=&amp;quot; + response.getCode());         System.out.println(&amp;quot;Message=&amp;quot; + response.getMessage());         System.out.println(&amp;quot;RequestId=&amp;quot; + response.getRequestId());         System.out.println(&amp;quot;BizId=&amp;quot; + response.getBizId());         Thread.sleep(3000L);         //查明细         if (response.getCode() != null &amp;amp;&amp;amp; response.getCode().equals(&amp;quot;OK&amp;quot;)) {             QuerySendDetailsResponse querySendDetailsResponse = querySendDetails(response.getBizId());             System.out.println(&amp;quot;短信明细查询接口返回数据----------------&amp;quot;);             System.out.println(&amp;quot;Code=&amp;quot; + querySendDetailsResponse.getCode());             System.out.println(&amp;quot;Message=&amp;quot; + querySendDetailsResponse.getMessage());             int i = 0;             for (QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO : querySendDetailsResponse.getSmsSendDetailDTOs()) {                 System.out.println(&amp;quot;SmsSendDetailDTO[&amp;quot; + i + &amp;quot;]:&amp;quot;);                 System.out.println(&amp;quot;Content=&amp;quot; + smsSendDetailDTO.getContent());                 System.out.println(&amp;quot;ErrCode=&amp;quot; + smsSendDetailDTO.getErrCode());                 System.out.println(&amp;quot;OutId=&amp;quot; + smsSendDetailDTO.getOutId());                 System.out.println(&amp;quot;PhoneNum=&amp;quot; + smsSendDetailDTO.getPhoneNum());                 System.out.println(&amp;quot;ReceiveDate=&amp;quot; + smsSendDetailDTO.getReceiveDate());                 System.out.println(&amp;quot;SendDate=&amp;quot; + smsSendDetailDTO.getSendDate());                 System.out.println(&amp;quot;SendStatus=&amp;quot; + smsSendDetailDTO.getSendStatus());                 System.out.println(&amp;quot;Template=&amp;quot; + smsSendDetailDTO.getTemplateCode());             }             System.out.println(&amp;quot;TotalCount=&amp;quot; + querySendDetailsResponse.getTotalCount());             System.out.println(&amp;quot;RequestId=&amp;quot; + querySendDetailsResponse.getRequestId());         }     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;下单之后发送短信&lt;/h2&gt; &lt;p&gt;在 shop-user 模块中加入sms依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!--短信发送--&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-alicloud-sms&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;将阿里短信给出的demo封装成工具类&lt;/p&gt; &lt;pre&gt;&lt;code class="language-\"&gt;import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile;  public class SmsUtil {     //替换成自己申请的accessKeyId     private static String accessKeyId = &amp;quot;LTAIMLlf8NKYXn1M&amp;quot;; //替换成自己申请的accessKeySecret     private static String accessKeySecret = &amp;quot;hqyW0zTNzeSIFnZhMEkOaZXVVcr3Gj&amp;quot;;     static final String product = &amp;quot;Dysmsapi&amp;quot;;     static final String domain = &amp;quot;dysmsapi.aliyuncs.com&amp;quot;;      /**      * \* 发送短信 *      * \* @param phoneNumbers 要发送短信到哪个手机号      * \* @param signName 短信签名[必须使用前面申请的]      * \* @param templateCode 短信短信模板ID[必须使用前面申请的]      * \* @param param 模板中${code}位置传递的内容      */     public static void sendSms(String phoneNumbers, String signName, String             templateCode, String param) {         try {             System.setProperty(&amp;quot;sun.net.client.defaultConnectTimeout&amp;quot;, &amp;quot;10000&amp;quot;);             System.setProperty(&amp;quot;sun.net.client.defaultReadTimeout&amp;quot;, &amp;quot;10000&amp;quot;); //初始化acsClient,暂不支持region化             IClientProfile profile = DefaultProfile.getProfile(&amp;quot;cn-hangzhou&amp;quot;,                     accessKeyId, accessKeySecret);             DefaultProfile.addEndpoint(&amp;quot;cn-hangzhou&amp;quot;, &amp;quot;cn-hangzhou&amp;quot;, product,                     domain);             IAcsClient acsClient = new DefaultAcsClient(profile);             SendSmsRequest request = new SendSmsRequest();             request.setPhoneNumbers(phoneNumbers);             request.setSignName(signName);             request.setTemplateCode(templateCode);             request.setTemplateParam(param);             request.setOutId(&amp;quot;yourOutId&amp;quot;);                          SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);             if (!&amp;quot;OK&amp;quot;.equals(sendSmsResponse.getCode())) {                 throw new RuntimeException(sendSmsResponse.getMessage());             }         } catch (Exception e) {             e.printStackTrace();             throw new RuntimeException(&amp;quot;发送短信失败&amp;quot;);         }      } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改短信发送的服务&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/* 发送短信的服务 */ @Slf4j @Service( &amp;quot;shopSmsService&amp;quot; ) @RocketMQMessageListener(         consumerGroup = &amp;quot;shop-user&amp;quot;, /* 消费组名 */         topic = &amp;quot;order-topic&amp;quot;,     /* 消费主题 */         consumeMode = ConsumeMode.CONCURRENTLY, /* 消费模式 */         messageModel = MessageModel.CLUSTERING /* 消息模式 */ ) public class SmsService implements RocketMQListener&amp;lt;Order&amp;gt; {      @Autowired     private UserDao userDao;      @Override     public void onMessage(Order message) {         log.info(&amp;quot;接收到了一个订单信息{},接下来就可以发送短信通知了&amp;quot;, message);         //  根据uid 获取手机号         User user = userDao.findById(message.getUid()).get();         //  生成验证码         StringBuilder builder = new StringBuilder();         for (int i = 0; i &amp;lt; 6; i++) {             builder.append(new Random().nextInt(9) + 1);         }         String smsCode = builder.toString();         Param param = new Param(smsCode);         try {             // 发送短信 {&amp;quot;code&amp;quot;:&amp;quot;123456&amp;quot;}             SmsUtil.sendSms(user.getTelephone(), &amp;quot;黑马旅游网&amp;quot;, &amp;quot;SMS_170836451&amp;quot;, JSON.toJSONString(param));             log.info(&amp;quot;短信发送成功&amp;quot;);         } catch (Exception e) {             e.printStackTrace();         }     }      @Data     @AllArgsConstructor     @NoArgsConstructor     class Param {         private String code;     } }  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 29 Aug 2021 08:20:29 GMT</pubDate>
    </item>
    <item>
      <title>使用群晖把阿里云盘挂载为本地系统的磁盘</title>
      <link>https://maruifu.cn/article/202</link>
      <content:encoded>&lt;h1 id="-ali-webdav"&gt;群晖使用ali-webdav&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;首先你的群晖支持docker&lt;/p&gt; &lt;/blockquote&gt; &lt;h2 id="ali-webdav-"&gt;ali-webdav下载&lt;/h2&gt; &lt;p&gt;在docker的注册表搜索 &lt;code&gt;ali-webdav&lt;/code&gt; 如图所示然后点击下载&lt;br&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810170701212.png" alt="image-20210810170701212"&gt;&lt;/p&gt; &lt;h2 id="ali-webdava-"&gt;ali-webdava安装&lt;/h2&gt; &lt;p&gt;映像中选中ali-webdava 点击启动&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810171329122.png" alt="image-20210810171329122"&gt;&lt;/p&gt; &lt;h3 id="-"&gt;高级设置&lt;/h3&gt; &lt;h4 id="-"&gt;启用自动重新启动&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810171451154.png" alt="image-20210810171451154"&gt;&lt;/p&gt; &lt;p&gt;作用是开机启动不用每次重新启动&lt;/p&gt; &lt;h4 id="-"&gt;挂载文件夹路径&lt;/h4&gt; &lt;p&gt;&lt;code&gt;/usr/local/java/docker/&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810171706996.png" alt="image-20210810171706996"&gt;&lt;/p&gt; &lt;p&gt;作用是1.如果token 失效可以直接替换文件夹中的token。2.可以直接查看日志&lt;/p&gt; &lt;h4 id="-"&gt;设置端口&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810172028901.png" alt="image-20210810172028901"&gt;&lt;/p&gt; &lt;p&gt;作用 后面可以页面访问，防止端口变化 &lt;/p&gt; &lt;h4 id="-"&gt;设置参数&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810172216014.png" alt="image-20210810172216014"&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="lang-bash"&gt;&lt;span class="token comment" spellcheck="true"&gt;# ALIYUNDRIVE_REFRESH_TOKEN 是你的refreshToken 必填参数&lt;/span&gt; &lt;span class="token comment" spellcheck="true"&gt;# 下面三个参数可以不做修改&lt;/span&gt; &lt;span class="token comment" spellcheck="true"&gt;# ALIYUNDRIVE_AUTH_ENABLE 是否开启WebDav账户验证，默认开启true 选填&lt;/span&gt; &lt;span class="token comment" spellcheck="true"&gt;# ALIYUNDRIVE_AUTH_USERNAME WebDav账户，默认admin 选填&lt;/span&gt; &lt;span class="token comment" spellcheck="true"&gt;# ALIYUNDRIVE_AUTH_PASSWORD WebDav密码，默认admin 选填&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt; &lt;h4 id="-"&gt;验证是否成功&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/10/image-20210810173239005.png" alt="image-20210810173239005"&gt;&lt;/p&gt; &lt;p&gt;输入你的群晖IP 加上刚才设置的端口号 我的地址是 &lt;a href="http://192.168.2.153:8088/"&gt;http://192.168.2.153:8088/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;如果出现访问页面则代表成功，如果不是的话 看一下日志提示的什么错误。&lt;/p&gt; &lt;h2 id="-"&gt;本地硬盘映射&lt;/h2&gt; &lt;p&gt;我使用的是RaiDrive 工具，windos自带的有点卡，这个很流畅，每秒拷贝 10M/s 左右&lt;br&gt;&lt;img src="https://img.maruifu.com/images/2021/08/11/image-20210811011142887.png" alt="image-20210811011142887"&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 10 Aug 2021 15:36:00 GMT</pubDate>
    </item>
    <item>
      <title>免费的SSL证书申请地址分享</title>
      <link>https://maruifu.cn/article/201</link>
      <content:encoded>&lt;p&gt;这里只提供申请地址，申请教程就不写了，排名无先后。&lt;/p&gt; &lt;p&gt;其中部分可免费申请多域名通配符证书&lt;/p&gt; &lt;p&gt;腾讯云：https://cloud.tencent.com/product/ssl&lt;/p&gt; &lt;p&gt;阿里云：https://www.aliyun.com/product/cas&lt;/p&gt; &lt;p&gt;又拍云：https://www.upyun.com/products/ssl&lt;/p&gt; &lt;p&gt;沃通：https://freessl.wosign.com/&lt;/p&gt; &lt;p&gt;七牛云：https://www.qiniu.com/ssl&lt;/p&gt; &lt;p&gt;FreeSSL：https://freessl.cn/&lt;/p&gt; &lt;p&gt;Let’s Encrypt：https://letsencrypt.org/&lt;/p&gt; &lt;p&gt;cloudflare：https://www.cloudflare.com/&lt;/p&gt; &lt;p&gt;FreeSSL：https://freessl.org&lt;/p&gt; &lt;p&gt;Buypass： https://www.buypass.com/&lt;/p&gt; &lt;p&gt;AMH: https://amh.sh/ssl.htm&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 09 Aug 2021 03:58:04 GMT</pubDate>
    </item>
    <item>
      <title>国内外在线接收短信验证码大全</title>
      <link>https://maruifu.cn/article/200</link>
      <content:encoded>&lt;p&gt;网上也发布过类似的文章，不过很多网站更换域名或停止访问不能用了；近期网友又整理了14个网址，可以用来免费在线接收验证码使用，可以防止自己隐私泄露；国内外手机号码都有，严禁用于非法使用，后果由使用者自担承担！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/08/image-20210808232657777.png" alt="image-20210808232657777" title="image-20210808232657777" /&gt;&lt;/p&gt; &lt;h1&gt;接码地址&lt;/h1&gt; &lt;p&gt;以下网站可用于在线接收验证码（网页界面广告广告有点多，但不影响使用）&lt;/p&gt; &lt;p&gt;https://www.bfkdim.com&lt;/p&gt; &lt;p&gt;https://www.materialtools.com&lt;/p&gt; &lt;p&gt;http://www.z-sms.com&lt;/p&gt; &lt;p&gt;https://www.yinsiduanxin.com/china-phone-number.html&lt;/p&gt; &lt;p&gt;https://www.zusms.com/phone/china&lt;/p&gt; &lt;p&gt;https://jieduanxin.com/China-Phone-Number&lt;/p&gt; &lt;p&gt;https://jiemahao.com&lt;/p&gt; &lt;p&gt;https://sms-receive.net&lt;/p&gt; &lt;p&gt;https://getfreesmsnumber.com (ua设置成pc或者电脑打开)&lt;/p&gt; &lt;p&gt;http://receive-sms-online.info&lt;/p&gt; &lt;p&gt;https://sms-online.co/receive-free-sms&lt;/p&gt; &lt;p&gt;https://f4.work/list_free.php?&lt;/p&gt; &lt;p&gt;http://lothelper.com/cn/shownumber?page=1list=PHONELIST_1_1_44&lt;/p&gt; &lt;p&gt;https://smsreceivefree.com&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 08 Aug 2021 15:27:27 GMT</pubDate>
    </item>
    <item>
      <title>联通官网免费申请155555手机靓号</title>
      <link>https://maruifu.cn/article/199</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;有人问我我手机号怎么买了，怎么这么好记，现在分享给大家！&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;今天分享给大家一个免费申请 联通手机15555AA手机靓号的方法，不要钱。网上很多人再买 几十 几百 几千 都有，其实这些号码都可以不要钱申请到的！不要再花钱买手机靓号啦，比如教程上面可以申请的 1565555这样的号码在“某宝”上面卖一千多呢。&lt;/p&gt; &lt;p&gt;今天教大家如何免费申请！！&lt;/p&gt; &lt;h2&gt;第一步：找到联通官网免费选号链接&lt;/h2&gt; &lt;p&gt;百度搜索“联通大新思维” 五个字，就可以看到联通的一个最新官方申请链接。&lt;/p&gt; &lt;p&gt;或者直接打开下面链接&lt;/p&gt; &lt;p&gt;http://www.10010.com/goodsdetail/301904268051.html&lt;/p&gt; &lt;h2&gt;第二步：选择对应号码地区&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;1555533/15555345选“芜湖”。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1555522/15555234选“宣城”&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1555511/166551155选“合肥”。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1555525/185551888选“亳州”&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1565555/1865555/1315555选“马鞍山”。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（马鞍山的有的有最低消费和预存款其他的不需要，号码都是全国通用的，发全国的，而且以后也可以携号转网。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;所以地区不需要纠结，号码喜欢就好.&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;第四步：选择自己喜欢的号码&lt;/h2&gt; &lt;p&gt;可以多选几页，多选几个上面的地区，然后看看有没有自己喜欢的。&lt;/p&gt; &lt;p&gt;然后确定喜欢号码，加入备选 下单就好了，不要钱的！赶快去看看吧！&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/08/image-20210808193638121.png" alt="image-20210808193638121" title="image-20210808193638121" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 08 Aug 2021 11:36:00 GMT</pubDate>
    </item>
    <item>
      <title>Centos7搭建JDK+Mysql+Redis</title>
      <link>https://maruifu.cn/article/198</link>
      <content:encoded>&lt;h1&gt;JDK&lt;/h1&gt; &lt;h2&gt;下载&lt;/h2&gt; &lt;p&gt;官网下载 JDK8：http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/07/20/image-20210720233057703.png" alt="image-20210720233057703" title="image-20210720233057703" /&gt;&lt;/p&gt; &lt;h2&gt;安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 创建目录 mkdir /usr/local/java # 拷贝安装包到指定目录 cp jdk-8u301-linux-x64.tar.gz   /usr/local/java/ # 进入java安装目录 cd /usr/local/java/ # 解压jdk安装包 tar -zxvf jdk-8u301-linux-x64.tar.gz # 删除安装包（可选） rm -rf jdk-8u301-linux-x64.tar.gz  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置环境变量&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 编辑配置文件 vim /etc/profile # 最下面添加如下内容： export JAVA_HOME=/usr/local/java/jdk1.8.0_301 export CLASSPATH=$JAVA_HOME/lib/ export PATH=$PATH:$JAVA_HOME/bin **************** 或者添加下面这种 JAVA_HOME=/usr/local/java/jdk1.8.0_301 CLASSPATH=$JAVA_HOME/lib/ PATH=$PATH:$JAVA_HOME/bin export PATH JAVA_HOME CLASSPATH  # 然后ESC :wq保存退出 :wq # 配置生效 source /etc/profile # 检查安装情况 java -version  &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;Maven&lt;/h1&gt; &lt;h2&gt;下载&lt;/h2&gt; &lt;p&gt;官网下载地址： https://maven.apache.org/download.cgi&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/08/25/image-20210825201326785.png" alt="image-20210825201326785" title="image-20210825201326785" /&gt;&lt;/p&gt; &lt;h2&gt;安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; # 创建目录 mkdir /usr/local/maven # 解压到创建目录下 tar -zxvf apache-maven-3.8.2-bin.tar.gz -C /usr/local/maven/ # 进入maven安装目录 下面有文件则没有问题 cd /usr/local/maven/apache-maven-3.8.2 ********************************************************* [root@xmg-gz apache-maven-3.8.2]# ls bin  boot  conf  lib  LICENSE  NOTICE  README.txt &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 编辑配置文件 vim /etc/profile # 最下面添加如下内容： export MAVEN_HOME=/usr/local/maven/apache-maven-3.8.2 export MAVEN_HOME export PATH=$PATH:$MAVEN_HOME/bin # 然后ESC :wq保存退出 :wq # 配置生效 source /etc/profile # 检查配置情况 mvn -version &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;MYSQL&lt;/h1&gt; &lt;h2&gt;背景信息&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;MySQL：5.7.33&lt;/p&gt; &lt;p&gt;MySQL相关安装路径说明如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;配置文件：/etc/my.cnf&lt;/li&gt; &lt;li&gt;数据存储：/var/lib/mysql&lt;/li&gt; &lt;li&gt;命令文件：/usr/bin和/usr/sbin&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据库端口：3306&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;安装mysql&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 更新YUM源 rpm -Uvh  https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm # 安装MySQL yum -y install mysql-community-server # 查看MySQL版本号 mysql -V  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置MySQL&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 启动MySQL服务 systemctl start mysqld # 设置MySQL服务开机自启动 systemctl enable mysqld # 查看/var/log/mysqld.log文件，获取并记录root用户的初始密码 grep 'temporary password' /var/log/mysqld.log # 执行命令结果示例如下。 2020-04-08T08:12:07.893939Z 1 [Note] A temporary password is generated for root@localhost: xvlo1lZ12&amp;gt;uI # 对MySQL进行安全性配置需要初始密码 mysql_secure_installation     &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安全性配置&lt;/h2&gt; &lt;h3&gt;重置root用户的密码&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#确保MySQL服务器部署的安全。 Securing the MySQL server deployment. #输入root用户的密码:  输入上一步获取的root用户初始密码 Enter password for user root:  #root用户的密码已经过期。请设置新密码。 The existing password for the user account root has expired. Please set a new password. #新密码: New password:  #重新输入新密码: Re-enter new password:  #'validate_password'插件安装在服务器上。 The 'validate_password' plugin is installed on the server. #后续步骤将使用现有配置运行的插件。 The subsequent steps will run with the existing configuration of the plugin. #使用现有的root密码。 Using existing password for root. #估计密码强度:100 Estimated strength of the password: 100  #修改root用户密码?(按y| y表示是，其他任何键表示否):y Change the password for root ? ((Press y|Y for Yes, any other key for No) : y #新密码: 长度为8至30个字符，必须同时包含大小写英文字母、数字和特殊符号。特殊符号可以是()` ~!@#$%^&amp;amp;*-+=|{}[]:;‘&amp;lt;&amp;gt;,.?/ New password:  #重新输入新密码: Re-enter new password:  #估计密码强度:100 Estimated strength of the password: 100  #您希望继续使用所提供的密码吗?(按y| y为Yes，按其他键为No): y Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;删除匿名用户账号&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#默认情况下，MySQL安装有一个匿名用户，允许任何人登录到MySQL，而无需为他们创建用户帐户。这只用于测试，并使安装过程更顺畅。您应该在转移到生产环境之前删除它们。 By default, a MySQL installation has an anonymous user, allowing anyone to log into MySQL without having to have a user account created for them. This is intended only for testing, and to make the installation go a bit smoother. You should remove them before moving into a production environment. #删除匿名用户?(按y| y为Yes，按其他键为No): Remove anonymous users? (Press y|Y for Yes, any other key for No) : Y  #是否删除匿名用户，输入Y Success. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;禁止root账号远程登录&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 通常，根应该只允许从“localhost”。这就保证了别人猜不到来自网络的根密码。 Normally, root should only be allowed to connect from 'localhost'. This ensures that someone cannot guess at the root password from the network.  #禁止root远程登录?(按y| y表示是，其他键表示否): Disallow root login remotely? (Press y|Y for Yes, any other key for No) : Y #禁止root远程登录，输入Y Success. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;删除test库以及对test库的访问权限&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; 默认情况下，MySQL自带一个名为“test”的数据库任何人都可以访问。这也仅用于测试，并且应该在进入生产之前被删除环境。 By default, MySQL comes with a database named 'test' that anyone can access. This is also intended only for testing,and should be removed before moving into a production environment.  删除测试数据库并访问它?(按y| y表示是，其他键表示否) Remove test database and access to it? (Press y|Y for Yes, any other key for No) : Y #是否删除test库和对它的访问权限，输入Y - Dropping test database... Success. &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;重新加载授权表&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#重新加载特权表将确保所有更改到目前为止所做的将立即生效。 Reloading the privilege tables will ensure that all changes made so far will take effect immediately.  #现在重新加载特权表?(按y| y表示是，其他键表示否) Reload privilege tables now? (Press y|Y for Yes, any other key for No) :  Success. All done! &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;安全性配置的更多信息，请参见&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/mysql-secure-installation.html" target="_blank"&gt;MySQL官方文档&lt;/a&gt;。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;远程访问MySQL数据库&lt;/h2&gt; &lt;h3&gt;配置安全组策略&lt;/h3&gt; &lt;p&gt;阿里云服务器安全组配置&amp;gt;配置规则&amp;gt;添加安全组  授权对象 0.0.0.0/0表示任意的IP。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;授权策略&lt;/th&gt;&lt;th align="left"&gt;优先级&lt;/th&gt;&lt;th align="left"&gt;协议类型&lt;/th&gt;&lt;th align="left"&gt;端口范围&lt;/th&gt;&lt;th align="left"&gt;授权对象&lt;/th&gt;&lt;th align="left"&gt;描述&lt;/th&gt;&lt;th align="left"&gt;创建时间&lt;/th&gt;&lt;th align="left"&gt;操作&lt;/th&gt;&lt;th&gt;&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;允许&lt;/td&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;自定义 TCP&lt;/td&gt;&lt;td align="left"&gt;目的: 3306/3306&lt;/td&gt;&lt;td align="left"&gt;源: 0.0.0.0/0&lt;/td&gt;&lt;td align="left"&gt;mysql数据库&lt;/td&gt;&lt;td align="left"&gt;2021年7月21日13:04:05&lt;/td&gt;&lt;td align="left"&gt;编辑复制删除&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;修改表法&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;# 进入mysql 输入密码 mysql -u root  -p   # 切换数据库 use mysql; # 修改数据库数据 update user set host = '%' where user = 'root'; # 查询修改结果 select host,user from user where user = 'root';  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;授权法&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;#你想myuser使用mypassword（密码）从任何主机连接到mysql服务器的话。   grant all on *.* to 'myuser'@'%' IDENTIFIED BY 'mypassword';  #使用root替换 myuser，可设置为允许root账号远程登录。  #如果你想允许用户myuser从ip为192.168.1.6的主机连接到mysql服务器，并使用mypassword作为密码    grant all on *.* to 'myuser'@'192.168.1.6' IDENTIFIED BY 'mypassword';  #使修改生效 FLUSH  PRIVILEGES &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;在采用授权法之后，无法在本地登录mysql 提示ERROR 1045 (28000): Access denied for user  'root'@'loadb116' (using password: YES) 上例中loadb116是主机名. 这时可以使用root进入后授权&lt;/p&gt; &lt;pre&gt;&lt;code&gt;grant all privileges on *.* to 'root'@'loadb116' identified by '123456' with grant option;   flush  privileges;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;或者使用本地IP登录&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;重启服务&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;systemctl restart mysqld &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;如果此时连不上，重启一下 reboot&lt;/p&gt; &lt;/blockquote&gt; &lt;h1&gt;Redis&lt;/h1&gt; &lt;h2&gt;redis下载&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;下载地址：&lt;/strong&gt;&lt;a href="https://link.segmentfault.com/?url=http%3A%2F%2Fredis.io%2Fdownload" target="_blank"&gt;http://redis.io/download&lt;/a&gt;，下载最新稳定版本。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; mkdir /usr/local/redis/ wget http://download.redis.io/releases/redis-6.0.8.tar.gz tar xzf redis-6.0.8.tar.gz   &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 进入redis目录 cd redis-6.0.8 # 安装 gcc  则直接进行编译 make yum -y install gcc # 如果不安装会编译报错  # -------------如果还报错  临时升级版本 start  gcc -v                             # 查看gcc版本    yum -y install centos-release-scl  # 升级到9.1版本    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils    scl enable devtoolset-9 bash   # -------------如果还报错  临时升级版本 end # 编译 make &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装成功会出现：Hint: It's a good idea to run 'make test'&lt;/p&gt; &lt;p&gt;执行make test 进行测试，如果出现如下错误：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@localhost redis-6.0.1]# make test cd src &amp;amp;&amp;amp; make test make[1]: 进入目录“/usr/redis-6.0.1/src”     CC Makefile.dep make[1]: 离开目录“/usr/redis-6.0.1/src” make[1]: 进入目录“/usr/redis-6.0.1/src” You need tcl 8.5 or newer in order to run the Redis test make[1]: *** [test] 错误 1 make[1]: 离开目录“/usr/redis-6.0.1/src” make: *** [test] 错误 2  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解决办法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# 安装 tcl yum install tcl  make test &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改配置文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 修改配置文件 vi redis.conf # 修改 #bind 127.0.0.1 为bind 0.0.0.0 -&amp;gt; 允许所有主机访问 69 # bind 127.0.0.1  70       bind 0.0.0.0 # 将daemonize no 改成 daemonize yes -&amp;gt; 设置redis可以一直在后台运行，以守护进程方式运行 225 daemonize yes #  密码设置，将”#requirepass foobared“ 取掉注释改成 requirepass 123456(或者其它你需要的密码) 791 requirepass 123456 GZA$bXZwnbe!6j  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;设置开机启动&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 创建存放redis的配置文件 mkdir /etc/redis # 创建存放redis的持久化文件 （可选） mkdir /var/redis/ mkdir /var/redis/6379  # 拷贝配置文件 cp /usr/local/redis/redis-6.0.8/redis.conf /etc/redis/6379.conf # 检查一下配置 daemonize yes       #让redis以daemon进程运行 pidfile  /var/run/redis_6379.pid  #设置redis的pid文件位置 port  6379      #设置redis的监听端口号 dir   /var/redis/6379    #设置持久化文件的存储位置 （可选）   # 拷贝脚本到开机启动脚本目录下 cp   /usr/local/redis/redis-6.0.8/utils/redis_init_script  /etc/init.d/redis_6379 cd /etc/init.d/  vim redis_6379 # 最上面，加入两行注释 # chkconfig:   2345 90 10 # description:  Redis is a persistent key-value database # 修改 以下两个地址 为自己的文件地址 EXEC=/usr/local/redis/redis-6.0.8/src/redis-server CLIEXEC=/usr/local/redis/redis-6.0.8/src/redis-cli # 授权  chmod 777 redis_6379 # 启动 redis ./redis_6379 start # 执行 加入开机启动 chkconfig redis_6379 on   &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 24 Jul 2021 04:34:00 GMT</pubDate>
    </item>
    <item>
      <title>Kubernetes简介和安装</title>
      <link>https://maruifu.cn/article/197</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;What is Kubernetes？&lt;/p&gt; &lt;p&gt;Kubernetes这个单词来自于希腊语，含义是 舵手 或 领航员&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;简介说明&lt;/h2&gt; &lt;p&gt;Production-Grade Container Orchestration Automated container deployment, scaling, and management&lt;/p&gt; &lt;p&gt;生产级的容器编排 自动化的容器部署、扩展和管理&lt;/p&gt; &lt;p&gt;Kubernetes，也称为K8S，其中8是代表中间“ubernete”的8个字符，是Google在2014年开源的一个容器编排引擎，用于自动化容器化应用程序的部署、规划、扩展和管理，它将组成应用程序的容器分组为逻辑单元，以便于管理和发现，用于管理云平台中多个主机上的容器化的应用，Kubernetes 的目标是让部署容器化的应用简单并且高效，很多细节都不需要运维人员去进行复杂的手工配置和处理；&lt;/p&gt; &lt;p&gt;Kubernetes拥有Google在生产环境上15年运行的经验，并结合了社区中最佳实践；&lt;/p&gt; &lt;p&gt;K8S是 &lt;a href="https://cncf.io/" target="_blank"&gt;CNCF&lt;/a&gt; 毕业的项目，本来Kubernetes是Google的内部项目，后来开源出来，又后来为了其茁壮成长，捐给了CNCF；&lt;/p&gt; &lt;p&gt;CNCF全称Cloud Native Computing Foundation（云原生计算基金会）&lt;/p&gt; &lt;p&gt;官网：https://kubernetes.io/&lt;/p&gt; &lt;p&gt;代码：https://github.com/kubernetes/kubernetes&lt;/p&gt; &lt;p&gt;Kubernetes是采用Go语言开发的，Go语言是谷歌2009发布的一款开源编程语言；&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;编排是什么意思？&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;按照一定的目的依次排列；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;调配、安排；&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/blockquote&gt; &lt;h2&gt;整体架构&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/06/02/2021-06-02-11.40.05.png" alt="截屏2021-06-02 下午11.40.05" title="截屏2021-06-02 下午11.40.05" /&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;Master&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;k8s集群控制节点，对集群进行调度管理，接受集群外用户去集群操作请求；&lt;/p&gt; &lt;p&gt;Master Node 由 API Server、Scheduler、ClusterState Store（ETCD 数据库）和 Controller MangerServer 所组成；&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;Nodes&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;集群工作节点，运行用户业务应用容器；&lt;/p&gt; &lt;p&gt;Nodes节点也叫Worker Node，包含kubelet、kube proxy 和 Pod（Container Runtime）；&lt;/p&gt; &lt;h2&gt;搭建方式&lt;/h2&gt; &lt;p&gt;部署 Kubernetes 环境（集群）主要有多种方式：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;minikube&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;minikube可以在本地运行Kubernetes的工具，minikube可以在个人计算机（包括Windows，macOS和Linux PC）上运行一个单节点Kubernetes集群，以便您可以试用Kubernetes或进行日常开发工作；&lt;/p&gt; &lt;p&gt;https://kubernetes.io/docs/tutorials/hello-minikube/&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;kind&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Kind和minikube类似的工具，让你在本地计算机上运行Kubernetes，此工具需要安装并配置Docker；&lt;/p&gt; &lt;p&gt;https://kind.sigs.k8s.io/&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt;kubeadm&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Kubeadm是一个K8s部署工具，提供kubeadm init 和 kubeadm join两个操作命令，可以快速部署一个Kubernetes集群；&lt;/p&gt; &lt;p&gt;官方地址：&lt;/p&gt; &lt;p&gt;https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/&lt;/p&gt; &lt;p&gt;https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;二进制包&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;从Github下载发行版的二进制包，手动部署安装每个组件，组成Kubernetes集群，步骤比较繁琐，但是能让你对各个组件有更清晰的认识；&lt;/p&gt; &lt;ol start="5"&gt; &lt;li&gt;yum安装&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;通过yum安装Kubernetes的每个组件，组成Kubernetes集群，不过yum源里面的k8s版本已经比较老了，所以这种方式用得也比较少了；&lt;/p&gt; &lt;ol start="6"&gt; &lt;li&gt;第三方工具&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;有一些大神封装了一些工具，利用这些工具进行k8s环境的安装；&lt;/p&gt; &lt;ol start="7"&gt; &lt;li&gt;花钱购买&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;直接购买类似阿里云这样的公有云平台k8s，一键搞定；&lt;/p&gt; &lt;h2&gt;Kubeadm部署&lt;/h2&gt; &lt;p&gt;kubeadm是官方社区推出的一个用于快速部署 kubernetes 集群的工具，这个工具能通过两条指令完成一个kubernetes集群的部署；&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;创建一个Master节点：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;kubeadm init &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;将Node节点加入到Master集群中：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ kubeadm join &amp;lt;Master节点的IP和端口&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;环境要求&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;一台或多台机器，操作系统CentOS 7.x-86_x64&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;硬件配置：内存2GB或2G+，CPU 2核或CPU 2核+；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;集群内各个机器之间能相互通信；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;集群内各个机器可以访问外网，需要拉取镜像；(非必须)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;禁止swap分区；&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;如果环境不满足要求，会报错，比如：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/06/02/2021-06-02-11.46.02.png" alt="截屏2021-06-02 下午11.46.02" title="截屏2021-06-02 下午11.46.02" /&gt;&lt;/p&gt; &lt;h3&gt;环境准备&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;关闭防火墙&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl stop firewalld systemctl disable firewalld &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;关闭selinux&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sed -i 's/enforcing/disabled/' /etc/selinux/config  #永久 setenforce 0  #临时 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;关闭swap（k8s禁止虚拟内存以提高性能）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sed -ri 's/.*swap.*/#&amp;amp;/' /etc/fstab #永久 swapoff -a #临时 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在master添加hosts&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cat &amp;gt;&amp;gt; /etc/hosts &amp;lt;&amp;lt; EOF 172.16.45.131 k8smaster 172.16.45.132 k8snode1 172.16.45.133 k8snode2 EOF &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;设置网桥参数&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl stop firewalld systemctl disable firewalld &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;时间同步&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl stop firewalld systemctl disable firewalld &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;安装步骤&lt;/h3&gt; &lt;p&gt;所有服务器节点安装 Docker/kubeadm/kubelet/kubectl&lt;/p&gt; &lt;p&gt;Kubernetes 默认容器运行环境是Docker，因此首先需要安装Docker；&lt;/p&gt; &lt;h4&gt;安装 Docker&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;#更新docker的yum源 yum install wget -y wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo #安装指定版本的docker： yum install docker-ce-19.03.13 -y #yum install docker -y （这个安装的Docker版本偏旧） 1.13.x  #配置加速器加速下载 登录该网址获取加速地址（https://cr.console.aliyun.com/）  sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json &amp;lt;&amp;lt;-'EOF' {   &amp;quot;registry-mirrors&amp;quot;: [&amp;quot;https://bz93554o.mirror.aliyuncs.com&amp;quot;] } EOF sudo systemctl daemon-reload sudo systemctl restart docker  #然后执行以下命令不然会提示警告； systemctl enable docker.service #那么接下来需要搭建：kubeadm、kubelet、kubectl  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;配置k8s的阿里云YUM源&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;cat &amp;gt; /etc/yum.repos.d/kubernetes.repo &amp;lt;&amp;lt; EOF [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;到时候下载k8s的相关组件才能找到下载源；&lt;/p&gt; &lt;h4&gt;安装 kubeadm，kubelet 和 kubectl&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;yum install kubelet-1.19.4 kubeadm-1.19.4 kubectl-1.19.4 -y #然后执行以下命令不然会提示警告； systemctl enable kubelet.service #查看有没有安装： yum list installed | grep kubelet yum list installed | grep kubeadm yum list installed | grep kubectl #查看安装的版本：  kubelet --version &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Kubelet：运行在cluster所有节点上，负责启动POD和容器；&lt;/p&gt; &lt;p&gt;Kubeadm：用于初始化cluster的一个工具；&lt;/p&gt; &lt;p&gt;Kubectl：kubectl是kubenetes命令行工具，通过kubectl可以部署和管理应用，查看各种资源，创建，删除和更新组件；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;切记&lt;/strong&gt;：此时应该重启一下centos；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;切记&lt;/strong&gt;：此时应该重启一下centos；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;切记&lt;/strong&gt;：此时应该重启一下centos；&lt;/p&gt; &lt;p&gt;重要的事情说三遍！！！&lt;/p&gt; &lt;h4&gt;部署Master主节点&lt;/h4&gt; &lt;p&gt;在master机器上执行以下命令；&lt;/p&gt; &lt;pre&gt;&lt;code&gt;kubeadm init --apiserver-advertise-address=172.16.45.131 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.19.4 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;说明：&lt;/p&gt; &lt;p&gt;service-cidr 的选取不能和PodCIDR及本机网络有重叠或者冲突，一般可以选择一个本机网络和PodCIDR都没有用到的私网地址段，比如PODCIDR使用10.244.0.0/16, 那么service cidr可以选择10.96.0.0/12，网络无重叠冲突即可；&lt;/p&gt; &lt;p&gt;执行成功后会显示以下信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@k8smaster ~]# kubeadm init --apiserver-advertise-address=172.16.45.131 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.19.4 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16 W0602 21:10:27.153412    8853 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io] [init] Using Kubernetes version: v1.19.4 [preflight] Running pre-flight checks         [WARNING IsDockerSystemdCheck]: detected &amp;quot;cgroupfs&amp;quot; as the Docker cgroup driver. The recommended driver is &amp;quot;systemd&amp;quot;. Please follow the guide at https://kubernetes.io/docs/setup/cri/ [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using 'kubeadm config images pull' [certs] Using certificateDir folder &amp;quot;/etc/kubernetes/pki&amp;quot; [certs] Generating &amp;quot;ca&amp;quot; certificate and key [certs] Generating &amp;quot;apiserver&amp;quot; certificate and key [certs] apiserver serving cert is signed for DNS names [k8smaster kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 172.16.45.131] [certs] Generating &amp;quot;apiserver-kubelet-client&amp;quot; certificate and key [certs] Generating &amp;quot;front-proxy-ca&amp;quot; certificate and key [certs] Generating &amp;quot;front-proxy-client&amp;quot; certificate and key [certs] Generating &amp;quot;etcd/ca&amp;quot; certificate and key [certs] Generating &amp;quot;etcd/server&amp;quot; certificate and key [certs] etcd/server serving cert is signed for DNS names [k8smaster localhost] and IPs [172.16.45.131 127.0.0.1 ::1] [certs] Generating &amp;quot;etcd/peer&amp;quot; certificate and key [certs] etcd/peer serving cert is signed for DNS names [k8smaster localhost] and IPs [172.16.45.131 127.0.0.1 ::1] [certs] Generating &amp;quot;etcd/healthcheck-client&amp;quot; certificate and key [certs] Generating &amp;quot;apiserver-etcd-client&amp;quot; certificate and key [certs] Generating &amp;quot;sa&amp;quot; key and public key [kubeconfig] Using kubeconfig folder &amp;quot;/etc/kubernetes&amp;quot; [kubeconfig] Writing &amp;quot;admin.conf&amp;quot; kubeconfig file [kubeconfig] Writing &amp;quot;kubelet.conf&amp;quot; kubeconfig file [kubeconfig] Writing &amp;quot;controller-manager.conf&amp;quot; kubeconfig file [kubeconfig] Writing &amp;quot;scheduler.conf&amp;quot; kubeconfig file [kubelet-start] Writing kubelet environment file with flags to file &amp;quot;/var/lib/kubelet/kubeadm-flags.env&amp;quot; [kubelet-start] Writing kubelet configuration to file &amp;quot;/var/lib/kubelet/config.yaml&amp;quot; [kubelet-start] Starting the kubelet [control-plane] Using manifest folder &amp;quot;/etc/kubernetes/manifests&amp;quot; [control-plane] Creating static Pod manifest for &amp;quot;kube-apiserver&amp;quot; [control-plane] Creating static Pod manifest for &amp;quot;kube-controller-manager&amp;quot; [control-plane] Creating static Pod manifest for &amp;quot;kube-scheduler&amp;quot; [etcd] Creating static Pod manifest for local etcd in &amp;quot;/etc/kubernetes/manifests&amp;quot; [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory &amp;quot;/etc/kubernetes/manifests&amp;quot;. This can take up to 4m0s [apiclient] All control plane components are healthy after 14.502742 seconds [upload-config] Storing the configuration used in ConfigMap &amp;quot;kubeadm-config&amp;quot; in the &amp;quot;kube-system&amp;quot; Namespace [kubelet] Creating a ConfigMap &amp;quot;kubelet-config-1.19&amp;quot; in namespace kube-system with the configuration for the kubelets in the cluster [upload-certs] Skipping phase. Please see --upload-certs [mark-control-plane] Marking the node k8smaster as control-plane by adding the label &amp;quot;node-role.kubernetes.io/master=''&amp;quot; [mark-control-plane] Marking the node k8smaster as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule] [bootstrap-token] Using token: uk5qox.2z2gpcq1qtjl7hlr [bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster [bootstrap-token] Creating the &amp;quot;cluster-info&amp;quot; ConfigMap in the &amp;quot;kube-public&amp;quot; namespace [kubelet-finalize] Updating &amp;quot;/etc/kubernetes/kubelet.conf&amp;quot; to point to a rotatable kubelet client certificate and key [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy  Your Kubernetes control-plane has initialized successfully!  To start using your cluster, you need to run the following as a regular user:    mkdir -p $HOME/.kube   sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config   sudo chown $(id -u):$(id -g) $HOME/.kube/config  You should now deploy a pod network to the cluster. Run &amp;quot;kubectl apply -f [podnetwork].yaml&amp;quot; with one of the options listed at:   https://kubernetes.io/docs/concepts/cluster-administration/addons/  Then you can join any number of worker nodes by running the following on each as root:  kubeadm join 172.16.45.131:6443 --token uk5qox.2z2gpcq1qtjl7hlr \     --discovery-token-ca-cert-hash sha256:ce6240b7d71a93309c46e99d450028d2d36fcb508960c557e6ce56bfaf0b1c58  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;接下来在master机器上继续执行：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看节点&lt;/p&gt; &lt;pre&gt;&lt;code&gt;kubectl get nodes &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;部署Node节点&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;kubeadm join 172.16.45.131:6443 --token uk5qox.2z2gpcq1qtjl7hlr \     --discovery-token-ca-cert-hash sha256:ce6240b7d71a93309c46e99d450028d2d36fcb508960c557e6ce56bfaf0b1c58  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;成功后会显示以下信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@k8snode1 ~]# kubeadm join 172.16.45.131:6443 --token uk5qox.2z2gpcq1qtjl7hlr \ &amp;gt;     --discovery-token-ca-cert-hash sha256:ce6240b7d71a93309c46e99d450028d2d36fcb508960c557e6ce56bfaf0b1c58  [preflight] Running pre-flight checks         [WARNING IsDockerSystemdCheck]: detected &amp;quot;cgroupfs&amp;quot; as the Docker cgroup driver. The recommended driver is &amp;quot;systemd&amp;quot;. Please follow the guide at https://kubernetes.io/docs/setup/cri/ [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [kubelet-start] Writing kubelet configuration to file &amp;quot;/var/lib/kubelet/config.yaml&amp;quot; [kubelet-start] Writing kubelet environment file with flags to file &amp;quot;/var/lib/kubelet/kubeadm-flags.env&amp;quot; [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...  This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details.  Run 'kubectl get nodes' on the control-plane to see this node join the cluster.  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时在master上查看节点显示以下信息说明加入成功&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@k8smaster ~]# kubectl get nodes NAME        STATUS   ROLES    AGE    VERSION k8smaster   NotReady    master   172m   v1.19.4 k8snode1    NotReady    &amp;lt;none&amp;gt;   120m   v1.19.4 k8snode2    NotReady    &amp;lt;none&amp;gt;   120m   v1.19.4  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;部署网络插件&lt;/h4&gt; &lt;p&gt;下载kube-flannel.yml文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;应用kube-flannel.yml文件得到运行时容器&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#在master机器上执行 kubectl apply -f kube-flannel.yml  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;[root@k8smaster ~]# kubectl apply -f kube-flannel.yml podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.apps/kube-flannel-ds created #（稍等几分钟后） 查看节点信息 会看见状态发生变化 [root@k8smaster ~]# kubectl get nodes NAME        STATUS   ROLES    AGE    VERSION k8smaster   Ready    master   172m   v1.19.4 k8snode1    Ready    &amp;lt;none&amp;gt;   120m   v1.19.4 k8snode2    Ready    &amp;lt;none&amp;gt;   120m   v1.19.4  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;至此我们的k8s环境就搭建好了；&lt;/p&gt; &lt;p&gt;查看运行时容器pod （一个pod里面运行了多个docker容器）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;kubectl get pods -n kube-system &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;部署容器化应用&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#部署nginx kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=NodePort kubectl get pod,svc #访问地址：http://NodeIP:Port #部署Tomcat： kubectl create deployment tomcat --image=tomcat kubectl expose deployment tomcat --port=8080 --type=NodePort #访问地址：http://NodeIP:Port  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;K8s部署微服务&lt;/h2&gt; &lt;p&gt;1、项目打包（jar、war）--&amp;gt;可以采用一些工具git、maven、jenkins&lt;/p&gt; &lt;p&gt;2、制作Dockerfile文件，生成镜像；&lt;/p&gt; &lt;p&gt;3、kubectl create deployment nginx --image= 你的镜像&lt;/p&gt; &lt;p&gt;4、你的springboot就部署好了，是以docker容器的方式运行在pod里面的；&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 02 Jun 2021 16:11:00 GMT</pubDate>
    </item>
    <item>
      <title>Mac VM CentOS7配置静态IP</title>
      <link>https://maruifu.cn/article/196</link>
      <content:encoded>&lt;h2&gt;把网络配置改成nat模式&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164805904-1878811384.png" alt="720430-20190610164805904-1878811384" title="720430-20190610164805904-1878811384" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164812040-64855612.png" alt="720430-20190610164812040-64855612" title="720430-20190610164812040-64855612" /&gt;&lt;/p&gt; &lt;h2&gt;获取网关地址和子网掩码&lt;/h2&gt; &lt;p&gt;通过Mac终端进入VMware Fusion的vmnet8目录&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd /Library/Preferences/VMware\ Fusion/vmnet8 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看nat.conf&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cat nat.conf &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;记住红框中的数据，下面配置时需要用到&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164827222-1773170825.png" alt="720430-20190610164827222-1773170825" title="720430-20190610164827222-1773170825" /&gt;&lt;/p&gt; &lt;h2&gt;获取可用IP地址&lt;/h2&gt; &lt;p&gt;查看cat dhcpd.conf&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cat dhcpd.conf &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164835758-181641312.png" alt="720430-20190610164835758-181641312" title="720430-20190610164835758-181641312" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;注意range 这个是虚拟机允许选择的静态ip地址范围，自定义的静态ip地址必须要在这个范围内(本文打算使用172.16.104.130为例介绍)&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;获取DNS1地址&lt;/h2&gt; &lt;p&gt;mac系统偏好设置—&amp;gt;网络—&amp;gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164843039-18488902.png" alt="720430-20190610164843039-18488902" title="720430-20190610164843039-18488902" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164849104-441757788.png" alt="720430-20190610164849104-441757788" title="720430-20190610164849104-441757788" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164855065-1139533275.png" alt="720430-20190610164855065-1139533275" title="720430-20190610164855065-1139533275" /&gt;&lt;/p&gt; &lt;h2&gt;配置CentOS7网络配置&lt;/h2&gt; &lt;p&gt;登录CentOS7进入虚拟机的network-scripts目录&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd /etc/sysconfig/network-scripts &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164947943-1391299598.png" alt="720430-20190610164947943-1391299598" title="720430-20190610164947943-1391299598" /&gt;&lt;/p&gt; &lt;p&gt;找到ifcfg-en开头的文件,上图中我的是ifcfg-ens33&lt;/p&gt; &lt;p&gt;通过vi编辑该文件&lt;/p&gt; &lt;p&gt;下图是默认配置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610164955215-574987874.png" alt="720430-20190610164955215-574987874" title="720430-20190610164955215-574987874" /&gt;&lt;/p&gt; &lt;p&gt;我们将它改成如下配置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610165000839-393578534.png" alt="720430-20190610165000839-393578534" title="720430-20190610165000839-393578534" /&gt;&lt;/p&gt; &lt;h2&gt;重启服务使修改生效&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;service network restart &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;测试配置是否成功&lt;/h2&gt; &lt;p&gt;ping一下百度看看，成功Ping到&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/720430-20190610165009727-1011741530.png" alt="720430-20190610165009727-1011741530" title="720430-20190610165009727-1011741530" /&gt;&lt;/p&gt; &lt;h2&gt;其他网络上网连接问题&lt;/h2&gt; &lt;p&gt;接下来我们就可以通过SecureCRT等工具远程连接了，有一点请记住，如果你换了一个地方上网的话，可能会发现你的虚拟机有不通了，那是因为DNS地址发生了变化，此时只需要再次编辑ifcfg-enxxx文件，然后加上你现在网络的DNS地址即可&lt;/p&gt; &lt;p&gt;如:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;DNS1=192.168.0.1 DNS2=114.114.114.114 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们通过SecureCRT连接测试一下&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/30/2021-05-30-11.35.08.png" alt="截屏2021-05-30 下午11.35.08" title="截屏2021-05-30 下午11.35.08" /&gt;&lt;/p&gt; &lt;h2&gt;其他命令&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;ifconfig  #查询自己的IP地址  netstat -rn #查询自己的默认网关和 #子网掩码  cat /etc/resolv.conf #查询自己的 DNS 配置 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 30 May 2021 15:40:00 GMT</pubDate>
    </item>
    <item>
      <title>Docker-常用安装与镜像发布到阿里云（四）</title>
      <link>https://maruifu.cn/article/195</link>
      <content:encoded>&lt;h2&gt;Docker常用安装&lt;/h2&gt; &lt;h3&gt;总体步骤&lt;/h3&gt; &lt;p&gt;搜索镜像-&amp;gt;拉取镜像-&amp;gt;查看镜像-&amp;gt;启动镜像-&amp;gt;停止容器-&amp;gt;移除容器&lt;/p&gt; &lt;h3&gt;安装tomcat&lt;/h3&gt; &lt;p&gt;1.docker hub上面查找tomcat镜像&lt;/p&gt; &lt;p&gt;&lt;code&gt;docker search tomcat&lt;/code&gt;&lt;/p&gt; &lt;p&gt;2.从docker hub上拉取tomcat镜像到本地&lt;/p&gt; &lt;p&gt;&lt;code&gt;docker pull tomcat&lt;/code&gt;&lt;/p&gt; &lt;p&gt;官网命令：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516183353962.png" alt="image-20210516183353962" title="image-20210516183353962" /&gt;&lt;/p&gt; &lt;p&gt;3.docker images查看是否有拉取到的tomcat&lt;/p&gt; &lt;p&gt;&lt;code&gt;docker images&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516183459819.png" alt="image-20210516183459819" title="image-20210516183459819" /&gt;&lt;/p&gt; &lt;p&gt;4.使用tomcat镜像创建容器(也叫运行镜像)&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -it -p 8080:8080 tomcat #  -p 主机端口:docker容器端口 #  -P 随机分配端口 #  i:交互 #  t:终端 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516183704111.png" alt="image-20210516183704111" title="image-20210516183704111" /&gt;&lt;/p&gt; &lt;h3&gt;安装mysql&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;docker hub上面查找mysql镜像&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516183851636.png" alt="image-20210516183851636" title="image-20210516183851636" /&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;从docker hub上(阿里云加速器)拉取mysql镜像到本地标签为5.6&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516183908178.png" alt="image-20210516183908178" title="image-20210516183908178" /&gt;&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt; &lt;p&gt;使用mysql5.6镜像创建容器(也叫运行镜像)&lt;/p&gt; &lt;p&gt;使用mysql镜像&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -p 12345:3306 --name mysql -v /zzyyuse/mysql/conf:/etc/mysql/conf.d -v /zzyyuse/mysql/logs:/logs -v /zzyyuse/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6  #命令说明： #-p 12345:3306：将主机的12345端口映射到docker容器的3306端口。 #--name mysql：运行服务名字 #-v /zzyyuse/mysql/conf:/etc/mysql/conf.d ：将主机/zzyyuse/mysql录下的conf/my.cnf 挂载到容器的 /etc/mysql/conf.d #-v /zzyyuse/mysql/logs:/logs：将主机/zzyyuse/mysql目录下的 logs 目录挂载到容器的 /logs。 #-v /zzyyuse/mysql/data:/var/lib/mysql ：将主机/zzyyuse/mysql目录下的data目录挂载到容器的 /var/lib/mysql  #-e MYSQL_ROOT_PASSWORD=123456：初始化 root 用户的密码。 #-d mysql:5.6 : 后台程序运行mysql5.6  docker exec -it MySQL运行成功后的容器ID     /bin/bash &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516184120015.png" alt="image-20210516184120015" title="image-20210516184120015" /&gt;&lt;/p&gt; &lt;p&gt;数据备份&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker exec myql服务容器ID sh -c ' exec mysqldump --all-databases -uroot -p&amp;quot;123456&amp;quot; ' &amp;gt; /zzyyuse/all-databases.sql &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;安装redis&lt;/h3&gt; &lt;p&gt;从docker hub上(阿里云加速器)拉取redis镜像到本地标签为3.2&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516184321294.png" alt="image-20210516184321294" title="image-20210516184321294" /&gt;&lt;/p&gt; &lt;p&gt;使用redis3.2镜像创建容器(也叫运行镜像)&lt;/p&gt; &lt;p&gt;使用镜像&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -p 6379:6379 -v /zzyyuse/myredis/data:/data -v /zzyyuse/myredis/conf/redis.conf:/usr/local/etc/redis/redis.conf  -d redis:3.2 redis-server /usr/local/etc/redis/redis.conf --appendonly yes &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在主机/zzyyuse/myredis/conf/redis.conf目录下新建redis.conf文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vim /zzyyuse/myredis/conf/redis.conf/redis.conf &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# Redis configuration file example. # Note that in order to read the configuration file, Redis must be # started with the file path as first argument: # # ./redis-server /path/to/redis.conf   # Note on units: when memory size is needed, it is possible to specify # it in the usual form of 1k 5GB 4M and so forth: # # 1k =&amp;gt; 1000 bytes # 1kb =&amp;gt; 1024 bytes # 1m =&amp;gt; 1000000 bytes # 1mb =&amp;gt; 1024*1024 bytes # 1g =&amp;gt; 1000000000 bytes # 1gb =&amp;gt; 1024*1024*1024 bytes # # units are case insensitive so 1GB 1Gb 1gB are all the same. ################################## INCLUDES ###################################   # Include one or more other config files here.  This is useful if you # have a standard template that goes to all Redis servers but also need # to customize a few per-server settings.  Include files can include # other files, so use this wisely. # # Notice option &amp;quot;include&amp;quot; won't be rewritten by command &amp;quot;CONFIG REWRITE&amp;quot; # from admin or Redis Sentinel. Since Redis always uses the last processed # line as value of a configuration directive, you'd better put includes # at the beginning of this file to avoid overwriting config change at runtime. # # If instead you are interested in using includes to override configuration # options, it is better to use include as the last line. # # include /path/to/local.conf # include /path/to/other.conf   ################################## NETWORK #####################################   # By default, if no &amp;quot;bind&amp;quot; configuration directive is specified, Redis listens # for connections from all the network interfaces available on the server. # It is possible to listen to just one or multiple selected interfaces using # the &amp;quot;bind&amp;quot; configuration directive, followed by one or more IP addresses. # # Examples: # # bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1 ::1 # # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the # internet, binding to all the interfaces is dangerous and will expose the # instance to everybody on the internet. So by default we uncomment the # following bind directive, that will force Redis to listen only into # the IPv4 lookback interface address (this means Redis will be able to # accept connections only from clients running into the same computer it # is running). # # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES # JUST COMMENT THE FOLLOWING LINE. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #bind 127.0.0.1   # Protected mode is a layer of security protection, in order to avoid that # Redis instances left open on the internet are accessed and exploited. # # When protected mode is on and if: # # 1) The server is not binding explicitly to a set of addresses using the #    &amp;quot;bind&amp;quot; directive. # 2) No password is configured. # # The server only accepts connections from clients connecting from the # IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain # sockets. # # By default protected mode is enabled. You should disable it only if # you are sure you want clients from other hosts to connect to Redis # even if no authentication is configured, nor a specific set of interfaces # are explicitly listed using the &amp;quot;bind&amp;quot; directive. protected-mode yes   # Accept connections on the specified port, default is 6379 (IANA #815344). # If port 0 is specified Redis will not listen on a TCP socket. port 6379   # TCP listen() backlog. # # In high requests-per-second environments you need an high backlog in order # to avoid slow clients connections issues. Note that the Linux kernel # will silently truncate it to the value of /proc/sys/net/core/somaxconn so # make sure to raise both the value of somaxconn and tcp_max_syn_backlog # in order to get the desired effect. tcp-backlog 511   # Unix socket. # # Specify the path for the Unix socket that will be used to listen for # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # # unixsocket /tmp/redis.sock # unixsocketperm 700   # Close the connection after a client is idle for N seconds (0 to disable) timeout 0   # TCP keepalive. # # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence # of communication. This is useful for two reasons: # # 1) Detect dead peers. # 2) Take the connection alive from the point of view of network #    equipment in the middle. # # On Linux, the specified value (in seconds) is the period used to send ACKs. # Note that to close the connection the double of the time is needed. # On other kernels the period depends on the kernel configuration. # # A reasonable value for this option is 300 seconds, which is the new # Redis default starting with Redis 3.2.1. tcp-keepalive 300   ################################# GENERAL #####################################   # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. #daemonize no   # If you run Redis from upstart or systemd, Redis can interact with your # supervision tree. Options: #   supervised no      - no supervision interaction #   supervised upstart - signal upstart by putting Redis into SIGSTOP mode #   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET #   supervised auto    - detect upstart or systemd method based on #                        UPSTART_JOB or NOTIFY_SOCKET environment variables # Note: these supervision methods only signal &amp;quot;process is ready.&amp;quot; #       They do not enable continuous liveness pings back to your supervisor. supervised no  # If a pid file is specified, Redis writes it where specified at startup # and removes it at exit. # # When the server runs non daemonized, no pid file is created if none is # specified in the configuration. When the server is daemonized, the pid file # is used even if not specified, defaulting to &amp;quot;/var/run/redis.pid&amp;quot;. # # Creating a pid file is best effort: if Redis is not able to create it # nothing bad happens, the server will start and run normally. pidfile /var/run/redis_6379.pid   # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) # warning (only very important / critical messages are logged) loglevel notice   # Specify the log file name. Also the empty string can be used to force # Redis to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile &amp;quot;&amp;quot;   # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no   # Specify the syslog identity. # syslog-ident redis   # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0   # Set the number of databases. The default database is DB 0, you can select # a different one on a per-connection basis using SELECT &amp;lt;dbid&amp;gt; where # dbid is a number between 0 and 'databases'-1 databases 16   ################################ SNAPSHOTTING  ################################ # # Save the DB on disk: # #   save &amp;lt;seconds&amp;gt; &amp;lt;changes&amp;gt; # #   Will save the DB if both the given number of seconds and the given #   number of write operations against the DB occurred. # #   In the example below the behaviour will be to save: #   after 900 sec (15 min) if at least 1 key changed #   after 300 sec (5 min) if at least 10 keys changed #   after 60 sec if at least 10000 keys changed # #   Note: you can disable saving completely by commenting out all &amp;quot;save&amp;quot; lines. # #   It is also possible to remove all the previously configured save #   points by adding a save directive with a single empty string argument #   like in the following example: # #   save &amp;quot;&amp;quot;   save 120 1 save 300 10 save 60 10000   # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. # This will make the user aware (in a hard way) that data is not persisting # on disk properly, otherwise chances are that no one will notice and some # disaster will happen. # # If the background saving process will start working again Redis will # automatically allow writes again. # # However if you have setup your proper monitoring of the Redis server # and persistence, you may want to disable this feature so that Redis will # continue to work as usual even if there are problems with disk, # permissions, and so forth. stop-writes-on-bgsave-error yes # Compress string objects using LZF when dump .rdb databases? # For default that's set to 'yes' as it's almost always a win. # If you want to save some CPU in the saving child set it to 'no' but # the dataset will likely be bigger if you have compressible values or keys. rdbcompression yes   # Since version 5 of RDB a CRC64 checksum is placed at the end of the file. # This makes the format more resistant to corruption but there is a performance # hit to pay (around 10%) when saving and loading RDB files, so you can disable it # for maximum performances. # # RDB files created with checksum disabled have a checksum of zero that will # tell the loading code to skip the check. rdbchecksum yes   # The filename where to dump the DB dbfilename dump.rdb   # The working directory. # # The DB will be written inside this directory, with the filename specified # above using the 'dbfilename' configuration directive. # # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. dir ./   ################################# REPLICATION #################################   # Master-Slave replication. Use slaveof to make a Redis instance a copy of # another Redis server. A few things to understand ASAP about Redis replication. # # 1) Redis replication is asynchronous, but you can configure a master to #    stop accepting writes if it appears to be not connected with at least #    a given number of slaves. # 2) Redis slaves are able to perform a partial resynchronization with the #    master if the replication link is lost for a relatively small amount of #    time. You may want to configure the replication backlog size (see the next #    sections of this file) with a sensible value depending on your needs. # 3) Replication is automatic and does not need user intervention. After a #    network partition slaves automatically try to reconnect to masters #    and resynchronize with them. # # slaveof &amp;lt;masterip&amp;gt; &amp;lt;masterport&amp;gt;   # If the master is password protected (using the &amp;quot;requirepass&amp;quot; configuration # directive below) it is possible to tell the slave to authenticate before # starting the replication synchronization process, otherwise the master will # refuse the slave request. # # masterauth &amp;lt;master-password&amp;gt;   # When a slave loses its connection with the master, or when the replication # is still in progress, the slave can act in two different ways: # # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will #    still reply to client requests, possibly with out of date data, or the #    data set may just be empty if this is the first synchronization. # # 2) if slave-serve-stale-data is set to 'no' the slave will reply with #    an error &amp;quot;SYNC with master in progress&amp;quot; to all the kind of commands #    but to INFO and SLAVEOF. # slave-serve-stale-data yes   # You can configure a slave instance to accept writes or not. Writing against # a slave instance may be useful to store some ephemeral data (because data # written on a slave will be easily deleted after resync with the master) but # may also cause problems if clients are writing to it because of a # misconfiguration. # # Since Redis 2.6 by default slaves are read-only. # # Note: read only slaves are not designed to be exposed to untrusted clients # on the internet. It's just a protection layer against misuse of the instance. # Still a read only slave exports by default all the administrative commands # such as CONFIG, DEBUG, and so forth. To a limited extent you can improve # security of read only slaves using 'rename-command' to shadow all the # administrative / dangerous commands. slave-read-only yes   # Replication SYNC strategy: disk or socket. # # ------------------------------------------------------- # WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY # ------------------------------------------------------- # # New slaves and reconnecting slaves that are not able to continue the replication # process just receiving differences, need to do what is called a &amp;quot;full # synchronization&amp;quot;. An RDB file is transmitted from the master to the slaves. # The transmission can happen in two different ways: # # 1) Disk-backed: The Redis master creates a new process that writes the RDB #                 file on disk. Later the file is transferred by the parent #                 process to the slaves incrementally. # 2) Diskless: The Redis master creates a new process that directly writes the #              RDB file to slave sockets, without touching the disk at all. # # With disk-backed replication, while the RDB file is generated, more slaves # can be queued and served with the RDB file as soon as the current child producing # the RDB file finishes its work. With diskless replication instead once # the transfer starts, new slaves arriving will be queued and a new transfer # will start when the current one terminates. # # When diskless replication is used, the master waits a configurable amount of # time (in seconds) before starting the transfer in the hope that multiple slaves # will arrive and the transfer can be parallelized. # # With slow disks and fast (large bandwidth) networks, diskless replication # works better. repl-diskless-sync no   # When diskless replication is enabled, it is possible to configure the delay # the server waits in order to spawn the child that transfers the RDB via socket # to the slaves. # # This is important since once the transfer starts, it is not possible to serve # new slaves arriving, that will be queued for the next RDB transfer, so the server # waits a delay in order to let more slaves arrive. # # The delay is specified in seconds, and by default is 5 seconds. To disable # it entirely just set it to 0 seconds and the transfer will start ASAP. repl-diskless-sync-delay 5   # Slaves send PINGs to server in a predefined interval. It's possible to change # this interval with the repl_ping_slave_period option. The default value is 10 # seconds. # # repl-ping-slave-period 10   # The following option sets the replication timeout for: # # 1) Bulk transfer I/O during SYNC, from the point of view of slave. # 2) Master timeout from the point of view of slaves (data, pings). # 3) Slave timeout from the point of view of masters (REPLCONF ACK pings). # # It is important to make sure that this value is greater than the value # specified for repl-ping-slave-period otherwise a timeout will be detected # every time there is low traffic between the master and the slave. # # repl-timeout 60   # Disable TCP_NODELAY on the slave socket after SYNC? # # If you select &amp;quot;yes&amp;quot; Redis will use a smaller number of TCP packets and # less bandwidth to send data to slaves. But this can add a delay for # the data to appear on the slave side, up to 40 milliseconds with # Linux kernels using a default configuration. # # If you select &amp;quot;no&amp;quot; the delay for data to appear on the slave side will # be reduced but more bandwidth will be used for replication. # # By default we optimize for low latency, but in very high traffic conditions # or when the master and slaves are many hops away, turning this to &amp;quot;yes&amp;quot; may # be a good idea. repl-disable-tcp-nodelay no   # Set the replication backlog size. The backlog is a buffer that accumulates # slave data when slaves are disconnected for some time, so that when a slave # wants to reconnect again, often a full resync is not needed, but a partial # resync is enough, just passing the portion of data the slave missed while # disconnected. # # The bigger the replication backlog, the longer the time the slave can be # disconnected and later be able to perform a partial resynchronization. # # The backlog is only allocated once there is at least a slave connected. # # repl-backlog-size 1mb   # After a master has no longer connected slaves for some time, the backlog # will be freed. The following option configures the amount of seconds that # need to elapse, starting from the time the last slave disconnected, for # the backlog buffer to be freed. # # A value of 0 means to never release the backlog. # # repl-backlog-ttl 3600   # The slave priority is an integer number published by Redis in the INFO output. # It is used by Redis Sentinel in order to select a slave to promote into a # master if the master is no longer working correctly. # # A slave with a low priority number is considered better for promotion, so # for instance if there are three slaves with priority 10, 100, 25 Sentinel will # pick the one with priority 10, that is the lowest. # # However a special priority of 0 marks the slave as not able to perform the # role of master, so a slave with priority of 0 will never be selected by # Redis Sentinel for promotion. # # By default the priority is 100. slave-priority 100  # It is possible for a master to stop accepting writes if there are less than # N slaves connected, having a lag less or equal than M seconds. # # The N slaves need to be in &amp;quot;online&amp;quot; state. # # The lag in seconds, that must be &amp;lt;= the specified value, is calculated from # the last ping received from the slave, that is usually sent every second. # # This option does not GUARANTEE that N replicas will accept the write, but # will limit the window of exposure for lost writes in case not enough slaves # are available, to the specified number of seconds. # # For example to require at least 3 slaves with a lag &amp;lt;= 10 seconds use: # # min-slaves-to-write 3 # min-slaves-max-lag 10 # # Setting one or the other to 0 disables the feature. # # By default min-slaves-to-write is set to 0 (feature disabled) and # min-slaves-max-lag is set to 10.   # A Redis master is able to list the address and port of the attached # slaves in different ways. For example the &amp;quot;INFO replication&amp;quot; section # offers this information, which is used, among other tools, by # Redis Sentinel in order to discover slave instances. # Another place where this info is available is in the output of the # &amp;quot;ROLE&amp;quot; command of a masteer. # # The listed IP and address normally reported by a slave is obtained # in the following way: # #   IP: The address is auto detected by checking the peer address #   of the socket used by the slave to connect with the master. # #   Port: The port is communicated by the slave during the replication #   handshake, and is normally the port that the slave is using to #   list for connections. # # However when port forwarding or Network Address Translation (NAT) is # used, the slave may be actually reachable via different IP and port # pairs. The following two options can be used by a slave in order to # report to its master a specific set of IP and port, so that both INFO # and ROLE will report those values. # # There is no need to use both the options if you need to override just # the port or the IP address. # # slave-announce-ip 5.5.5.5 # slave-announce-port 1234   ################################## SECURITY ###################################   # Require clients to issue AUTH &amp;lt;PASSWORD&amp;gt; before processing any other # commands.  This might be useful in environments in which you do not trust # others with access to the host running redis-server. # # This should stay commented out for backward compatibility and because most # people do not need auth (e.g. they run their own servers). # # Warning: since Redis is pretty fast an outside user can try up to # 150k passwords per second against a good box. This means that you should # use a very strong password otherwise it will be very easy to break. # # requirepass foobared   # Command renaming. # # It is possible to change the name of dangerous commands in a shared # environment. For instance the CONFIG command may be renamed into something # hard to guess so that it will still be available for internal-use tools # but not available for general clients. # # Example: # # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 # # It is also possible to completely kill a command by renaming it into # an empty string: # # rename-command CONFIG &amp;quot;&amp;quot; # # Please note that changing the name of commands that are logged into the # AOF file or transmitted to slaves may cause problems.   ################################### LIMITS ####################################   # Set the max number of connected clients at the same time. By default # this limit is set to 10000 clients, however if the Redis server is not # able to configure the process file limit to allow for the specified limit # the max number of allowed clients is set to the current file limit # minus 32 (as Redis reserves a few file descriptors for internal uses). # # Once the limit is reached Redis will close all the new connections sending # an error 'max number of clients reached'. # # maxclients 10000   # Don't use more memory than the specified amount of bytes. # When the memory limit is reached Redis will try to remove keys # according to the eviction policy selected (see maxmemory-policy). # # If Redis can't remove keys according to the policy, or if the policy is # set to 'noeviction', Redis will start to reply with errors to commands # that would use more memory, like SET, LPUSH, and so on, and will continue # to reply to read-only commands like GET. # # This option is usually useful when using Redis as an LRU cache, or to set # a hard memory limit for an instance (using the 'noeviction' policy). # # WARNING: If you have slaves attached to an instance with maxmemory on, # the size of the output buffers needed to feed the slaves are subtracted # from the used memory count, so that network problems / resyncs will # not trigger a loop where keys are evicted, and in turn the output # buffer of slaves is full with DELs of keys evicted triggering the deletion # of more keys, and so forth until the database is completely emptied. # # In short... if you have slaves attached it is suggested that you set a lower # limit for maxmemory so that there is some free RAM on the system for slave # output buffers (but this is not needed if the policy is 'noeviction'). # # maxmemory &amp;lt;bytes&amp;gt;   # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select among five behaviors: # # volatile-lru -&amp;gt; remove the key with an expire set using an LRU algorithm # allkeys-lru -&amp;gt; remove any key according to the LRU algorithm # volatile-random -&amp;gt; remove a random key with an expire set # allkeys-random -&amp;gt; remove a random key, any key # volatile-ttl -&amp;gt; remove the key with the nearest expire time (minor TTL) # noeviction -&amp;gt; don't expire at all, just return an error on write operations # # Note: with any of the above policies, Redis will return an error on write #       operations, when there are no suitable keys for eviction. # #       At the date of writing these commands are: set setnx setex append #       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd #       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby #       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby #       getset mset msetnx exec sort # # The default is: # # maxmemory-policy noeviction   # LRU and minimal TTL algorithms are not precise algorithms but approximated # algorithms (in order to save memory), so you can tune it for speed or # accuracy. For default Redis will check five keys and pick the one that was # used less recently, you can change the sample size using the following # configuration directive. # # The default of 5 produces good enough results. 10 Approximates very closely # true LRU but costs a bit more CPU. 3 is very fast but not very accurate. # # maxmemory-samples 5   ############################## APPEND ONLY MODE ###############################   # By default Redis asynchronously dumps the dataset on disk. This mode is # good enough in many applications, but an issue with the Redis process or # a power outage may result into a few minutes of writes lost (depending on # the configured save points). # # The Append Only File is an alternative persistence mode that provides # much better durability. For instance using the default data fsync policy # (see later in the config file) Redis can lose just one second of writes in a # dramatic event like a server power outage, or a single write if something # wrong with the Redis process itself happens, but the operating system is # still running correctly. # # AOF and RDB persistence can be enabled at the same time without problems. # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # # Please check http://redis.io/topics/persistence for more information.   appendonly no   # The name of the append only file (default: &amp;quot;appendonly.aof&amp;quot;)   appendfilename &amp;quot;appendonly.aof&amp;quot;   # The fsync() call tells the Operating System to actually write data on disk # instead of waiting for more data in the output buffer. Some OS will really flush # data on disk, some other OS will just try to do it ASAP. # # Redis supports three different modes: # # no: don't fsync, just let the OS flush the data when it wants. Faster. # always: fsync after every write to the append only log. Slow, Safest. # everysec: fsync only one time every second. Compromise. # # The default is &amp;quot;everysec&amp;quot;, as that's usually the right compromise between # speed and data safety. It's up to you to understand if you can relax this to # &amp;quot;no&amp;quot; that will let the operating system flush the output buffer when # it wants, for better performances (but if you can live with the idea of # some data loss consider the default persistence mode that's snapshotting), # or on the contrary, use &amp;quot;always&amp;quot; that's very slow but a bit safer than # everysec. # # More details please check the following article: # http://antirez.com/post/redis-persistence-demystified.html # # If unsure, use &amp;quot;everysec&amp;quot;.   # appendfsync always appendfsync everysec # appendfsync no   # When the AOF fsync policy is set to always or everysec, and a background # saving process (a background save or AOF log background rewriting) is # performing a lot of I/O against the disk, in some Linux configurations # Redis may block too long on the fsync() call. Note that there is no fix for # this currently, as even performing fsync in a different thread will block # our synchronous write(2) call. # # In order to mitigate this problem it's possible to use the following option # that will prevent fsync() from being called in the main process while a # BGSAVE or BGREWRITEAOF is in progress. # # This means that while another child is saving, the durability of Redis is # the same as &amp;quot;appendfsync none&amp;quot;. In practical terms, this means that it is # possible to lose up to 30 seconds of log in the worst scenario (with the # default Linux settings). # # If you have latency problems turn this to &amp;quot;yes&amp;quot;. Otherwise leave it as # &amp;quot;no&amp;quot; that is the safest pick from the point of view of durability.   no-appendfsync-on-rewrite no   # Automatic rewrite of the append only file. # Redis is able to automatically rewrite the log file implicitly calling # BGREWRITEAOF when the AOF log size grows by the specified percentage. # # This is how it works: Redis remembers the size of the AOF file after the # latest rewrite (if no rewrite has happened since the restart, the size of # the AOF at startup is used). # # This base size is compared to the current size. If the current size is # bigger than the specified percentage, the rewrite is triggered. Also # you need to specify a minimal size for the AOF file to be rewritten, this # is useful to avoid rewriting the AOF file even if the percentage increase # is reached but it is still pretty small. # # Specify a percentage of zero in order to disable the automatic AOF # rewrite feature.   auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb  # An AOF file may be found to be truncated at the end during the Redis # startup process, when the AOF data gets loaded back into memory. # This may happen when the system where Redis is running # crashes, especially when an ext4 filesystem is mounted without the # data=ordered option (however this can't happen when Redis itself # crashes or aborts but the operating system still works correctly). # # Redis can either exit with an error when this happens, or load as much # data as possible (the default now) and start if the AOF file is found # to be truncated at the end. The following option controls this behavior. # # If aof-load-truncated is set to yes, a truncated AOF file is loaded and # the Redis server starts emitting a log to inform the user of the event. # Otherwise if the option is set to no, the server aborts with an error # and refuses to start. When the option is set to no, the user requires # to fix the AOF file using the &amp;quot;redis-check-aof&amp;quot; utility before to restart # the server. # # Note that if the AOF file will be found to be corrupted in the middle # the server will still exit with an error. This option only applies when # Redis will try to read more data from the AOF file but not enough bytes # will be found. aof-load-truncated yes   ################################ LUA SCRIPTING  ###############################   # Max execution time of a Lua script in milliseconds. # # If the maximum execution time is reached Redis will log that a script is # still in execution after the maximum allowed time and will start to # reply to queries with an error. # # When a long running script exceeds the maximum execution time only the # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be # used to stop a script that did not yet called write commands. The second # is the only way to shut down the server in the case a write command was # already issued by the script but the user doesn't want to wait for the natural # termination of the script. # # Set it to 0 or a negative value for unlimited execution without warnings. lua-time-limit 5000   ################################ REDIS CLUSTER  ############################### # # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however # in order to mark it as &amp;quot;mature&amp;quot; we need to wait for a non trivial percentage # of users to deploy it in production. # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Normal Redis instances can't be part of a Redis Cluster; only nodes that are # started as cluster nodes can. In order to start a Redis instance as a # cluster node enable the cluster support uncommenting the following: # # cluster-enabled yes   # Every cluster node has a cluster configuration file. This file is not # intended to be edited by hand. It is created and updated by Redis nodes. # Every Redis Cluster node requires a different cluster configuration file. # Make sure that instances running in the same system do not have # overlapping cluster configuration file names. # # cluster-config-file nodes-6379.conf   # Cluster node timeout is the amount of milliseconds a node must be unreachable # for it to be considered in failure state. # Most other internal time limits are multiple of the node timeout. # # cluster-node-timeout 15000   # A slave of a failing master will avoid to start a failover if its data # looks too old. # # There is no simple way for a slave to actually have a exact measure of # its &amp;quot;data age&amp;quot;, so the following two checks are performed: # # 1) If there are multiple slaves able to failover, they exchange messages #    in order to try to give an advantage to the slave with the best #    replication offset (more data from the master processed). #    Slaves will try to get their rank by offset, and apply to the start #    of the failover a delay proportional to their rank. # # 2) Every single slave computes the time of the last interaction with #    its master. This can be the last ping or command received (if the master #    is still in the &amp;quot;connected&amp;quot; state), or the time that elapsed since the #    disconnection with the master (if the replication link is currently down). #    If the last interaction is too old, the slave will not try to failover #    at all. # # The point &amp;quot;2&amp;quot; can be tuned by user. Specifically a slave will not perform # the failover if, since the last interaction with the master, the time # elapsed is greater than: # #   (node-timeout * slave-validity-factor) + repl-ping-slave-period # # So for example if node-timeout is 30 seconds, and the slave-validity-factor # is 10, and assuming a default repl-ping-slave-period of 10 seconds, the # slave will not try to failover if it was not able to talk with the master # for longer than 310 seconds. # # A large slave-validity-factor may allow slaves with too old data to failover # a master, while a too small value may prevent the cluster from being able to # elect a slave at all. # # For maximum availability, it is possible to set the slave-validity-factor # to a value of 0, which means, that slaves will always try to failover the # master regardless of the last time they interacted with the master. # (However they'll always try to apply a delay proportional to their # offset rank). # # Zero is the only value able to guarantee that when all the partitions heal # the cluster will always be able to continue. # # cluster-slave-validity-factor 10   # Cluster slaves are able to migrate to orphaned masters, that are masters # that are left without working slaves. This improves the cluster ability # to resist to failures as otherwise an orphaned master can't be failed over # in case of failure if it has no working slaves. # # Slaves migrate to orphaned masters only if there are still at least a # given number of other working slaves for their old master. This number # is the &amp;quot;migration barrier&amp;quot;. A migration barrier of 1 means that a slave # will migrate only if there is at least 1 other working slave for its master # and so forth. It usually reflects the number of slaves you want for every # master in your cluster. # # Default is 1 (slaves migrate only if their masters remain with at least # one slave). To disable migration just set it to a very large value. # A value of 0 can be set but is useful only for debugging and dangerous # in production. # # cluster-migration-barrier 1   # By default Redis Cluster nodes stop accepting queries if they detect there # is at least an hash slot uncovered (no available node is serving it). # This way if the cluster is partially down (for example a range of hash slots # are no longer covered) all the cluster becomes, eventually, unavailable. # It automatically returns available as soon as all the slots are covered again. # # However sometimes you want the subset of the cluster which is working, # to continue to accept queries for the part of the key space that is still # covered. In order to do so, just set the cluster-require-full-coverage # option to no. # # cluster-require-full-coverage yes   # In order to setup your cluster make sure to read the documentation # available at http://redis.io web site.   ################################## SLOW LOG ###################################   # The Redis Slow Log is a system to log queries that exceeded a specified # execution time. The execution time does not include the I/O operations # like talking with the client, sending the reply and so forth, # but just the time needed to actually execute the command (this is the only # stage of command execution where the thread is blocked and can not serve # other requests in the meantime). # # You can configure the slow log with two parameters: one tells Redis # what is the execution time, in microseconds, to exceed in order for the # command to get logged, and the other parameter is the length of the # slow log. When a new command is logged the oldest one is removed from the # queue of logged commands.   # The following time is expressed in microseconds, so 1000000 is equivalent # to one second. Note that a negative number disables the slow log, while # a value of zero forces the logging of every command. slowlog-log-slower-than 10000   # There is no limit to this length. Just be aware that it will consume memory. # You can reclaim memory used by the slow log with SLOWLOG RESET. slowlog-max-len 128   ################################ LATENCY MONITOR ##############################   # The Redis latency monitoring subsystem samples different operations # at runtime in order to collect data related to possible sources of # latency of a Redis instance. # # Via the LATENCY command this information is available to the user that can # print graphs and obtain reports. # # The system only logs operations that were performed in a time equal or # greater than the amount of milliseconds specified via the # latency-monitor-threshold configuration directive. When its value is set # to zero, the latency monitor is turned off. # # By default latency monitoring is disabled since it is mostly not needed # if you don't have latency issues, and collecting data has a performance # impact, that while very small, can be measured under big load. Latency # monitoring can easily be enabled at runtime using the command # &amp;quot;CONFIG SET latency-monitor-threshold &amp;lt;milliseconds&amp;gt;&amp;quot; if needed. latency-monitor-threshold 0   ############################# EVENT NOTIFICATION ##############################   # Redis can notify Pub/Sub clients about events happening in the key space. # This feature is documented at http://redis.io/topics/notifications # # For instance if keyspace events notification is enabled, and a client # performs a DEL operation on key &amp;quot;foo&amp;quot; stored in the Database 0, two # messages will be published via Pub/Sub: # # PUBLISH __keyspace@0__:foo del # PUBLISH __keyevent@0__:del foo # # It is possible to select the events that Redis will notify among a set # of classes. Every class is identified by a single character: # #  K     Keyspace events, published with __keyspace@&amp;lt;db&amp;gt;__ prefix. #  E     Keyevent events, published with __keyevent@&amp;lt;db&amp;gt;__ prefix. #  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... #  $     String commands #  l     List commands #  s     Set commands #  h     Hash commands #  z     Sorted set commands #  x     Expired events (events generated every time a key expires) #  e     Evicted events (events generated when a key is evicted for maxmemory) #  A     Alias for g$lshzxe, so that the &amp;quot;AKE&amp;quot; string means all the events. # #  The &amp;quot;notify-keyspace-events&amp;quot; takes as argument a string that is composed #  of zero or multiple characters. The empty string means that notifications #  are disabled. # #  Example: to enable list and generic events, from the point of view of the #           event name, use: # #  notify-keyspace-events Elg # #  Example 2: to get the stream of the expired keys subscribing to channel #             name __keyevent@0__:expired use: # #  notify-keyspace-events Ex # #  By default all notifications are disabled because most users don't need #  this feature and the feature has some overhead. Note that if you don't #  specify at least one of K or E, no events will be delivered. notify-keyspace-events &amp;quot;&amp;quot;   ############################### ADVANCED CONFIG ###############################   # Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. hash-max-ziplist-entries 512 hash-max-ziplist-value 64   # Lists are also encoded in a special way to save a lot of space. # The number of entries allowed per internal list node can be specified # as a fixed maximum size or a maximum number of elements. # For a fixed maximum size, use -5 through -1, meaning: # -5: max size: 64 Kb  &amp;lt;-- not recommended for normal workloads # -4: max size: 32 Kb  &amp;lt;-- not recommended # -3: max size: 16 Kb  &amp;lt;-- probably not recommended # -2: max size: 8 Kb   &amp;lt;-- good # -1: max size: 4 Kb   &amp;lt;-- good # Positive numbers mean store up to _exactly_ that number of elements # per list node. # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), # but if your use case is unique, adjust the settings as necessary. list-max-ziplist-size -2   # Lists may also be compressed. # Compress depth is the number of quicklist ziplist nodes from *each* side of # the list to *exclude* from compression.  The head and tail of the list # are always uncompressed for fast push/pop operations.  Settings are: # 0: disable all list compression # 1: depth 1 means &amp;quot;don't start compressing until after 1 node into the list, #    going from either the head or tail&amp;quot; #    So: [head]-&amp;gt;node-&amp;gt;node-&amp;gt;...-&amp;gt;node-&amp;gt;[tail] #    [head], [tail] will always be uncompressed; inner nodes will compress. # 2: [head]-&amp;gt;[next]-&amp;gt;node-&amp;gt;node-&amp;gt;...-&amp;gt;node-&amp;gt;[prev]-&amp;gt;[tail] #    2 here means: don't compress head or head-&amp;gt;next or tail-&amp;gt;prev or tail, #    but compress all nodes between them. # 3: [head]-&amp;gt;[next]-&amp;gt;[next]-&amp;gt;node-&amp;gt;node-&amp;gt;...-&amp;gt;node-&amp;gt;[prev]-&amp;gt;[prev]-&amp;gt;[tail] # etc. list-compress-depth 0   # Sets have a special encoding in just one case: when a set is composed # of just strings that happen to be integers in radix 10 in the range # of 64 bit signed integers. # The following configuration setting sets the limit in the size of the # set in order to use this special memory saving encoding. set-max-intset-entries 512   # Similarly to hashes and lists, sorted sets are also specially encoded in # order to save a lot of space. This encoding is only used when the length and # elements of a sorted set are below the following limits: zset-max-ziplist-entries 128 zset-max-ziplist-value 64  # HyperLogLog sparse representation bytes limit. The limit includes the # 16 bytes header. When an HyperLogLog using the sparse representation crosses # this limit, it is converted into the dense representation. # # A value greater than 16000 is totally useless, since at that point the # dense representation is more memory efficient. # # The suggested value is ~ 3000 in order to have the benefits of # the space efficient encoding without slowing down too much PFADD, # which is O(N) with the sparse encoding. The value can be raised to # ~ 10000 when CPU is not a concern, but space is, and the data set is # composed of many HyperLogLogs with cardinality in the 0 - 15000 range. hll-sparse-max-bytes 3000   # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level # keys to values). The hash table implementation Redis uses (see dict.c) # performs a lazy rehashing: the more operation you run into a hash table # that is rehashing, the more rehashing &amp;quot;steps&amp;quot; are performed, so if the # server is idle the rehashing is never complete and some more memory is used # by the hash table. # # The default is to use this millisecond 10 times every second in order to # actively rehash the main dictionaries, freeing memory when possible. # # If unsure: # use &amp;quot;activerehashing no&amp;quot; if you have hard latency requirements and it is # not a good thing in your environment that Redis can reply from time to time # to queries with 2 milliseconds delay. # # use &amp;quot;activerehashing yes&amp;quot; if you don't have such hard requirements but # want to free memory asap when possible. activerehashing yes # The client output buffer limits can be used to force disconnection of clients # that are not reading data from the server fast enough for some reason (a # common reason is that a Pub/Sub client can't consume messages as fast as the # publisher can produce them). # # The limit can be set differently for the three different classes of clients: # # normal -&amp;gt; normal clients including MONITOR clients # slave  -&amp;gt; slave clients # pubsub -&amp;gt; clients subscribed to at least one pubsub channel or pattern # # The syntax of every client-output-buffer-limit directive is the following: # # client-output-buffer-limit &amp;lt;class&amp;gt; &amp;lt;hard limit&amp;gt; &amp;lt;soft limit&amp;gt; &amp;lt;soft seconds&amp;gt; # # A client is immediately disconnected once the hard limit is reached, or if # the soft limit is reached and remains reached for the specified number of # seconds (continuously). # So for instance if the hard limit is 32 megabytes and the soft limit is # 16 megabytes / 10 seconds, the client will get disconnected immediately # if the size of the output buffers reach 32 megabytes, but will also get # disconnected if the client reaches 16 megabytes and continuously overcomes # the limit for 10 seconds. # # By default normal clients are not limited because they don't receive data # without asking (in a push way), but just after a request, so only # asynchronous clients may create a scenario where data is requested faster # than it can read. # # Instead there is a default limit for pubsub and slave clients, since # subscribers and slaves receive data in a push fashion. # # Both the hard or the soft limit can be disabled by setting them to zero. client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # Redis calls an internal function to perform many background tasks, like # closing connections of clients in timeout, purging expired keys that are # never requested, and so forth. # # Not all tasks are performed with the same frequency, but Redis checks for # tasks to perform according to the specified &amp;quot;hz&amp;quot; value. # # By default &amp;quot;hz&amp;quot; is set to 10. Raising the value will use more CPU when # Redis is idle, but at the same time will make Redis more responsive when # there are many keys expiring at the same time, and timeouts may be # handled with more precision. # # The range is between 1 and 500, however a value over 100 is usually not # a good idea. Most users should use the default of 10 and raise this up to # 100 only in environments where very low latency is required. hz 10 # When a child rewrites the AOF file, if the following option is enabled # the file will be fsync-ed every 32 MB of data generated. This is useful # in order to commit the file to the disk more incrementally and avoid # big latency spikes. aof-rewrite-incremental-fsync yes &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;测试redis-cli连接上来&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker exec -it 运行着Rediis服务的容器ID redis-cli &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516185657343.png" alt="image-20210516185657343" title="image-20210516185657343" /&gt;&lt;/p&gt; &lt;p&gt;测试持久化文件生成&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516185719737.png" alt="image-20210516185719737" title="image-20210516185719737" /&gt;&lt;/p&gt; &lt;h2&gt;本地镜像发布到阿里云&lt;/h2&gt; &lt;h3&gt;本地镜像发布到阿里云流程&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516193430674.png" alt="image-20210516193430674" title="image-20210516193430674" /&gt;&lt;/p&gt; &lt;h3&gt;镜像的生成方法&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;前面的DockerFile&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;从容器创建一个新的镜像&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker commit [OPTIONS] 容器ID [REPOSITORY[:TAG]] # OPTIONS说明： # -a :提交的镜像作者； # -m :提交时的说明文字； &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;本地镜像推送到阿里云&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;本地镜像素材原型&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194014803.png" alt="image-20210516194014803" title="image-20210516194014803" /&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;阿里云开发者平台&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194033120.png" alt="image-20210516194033120" title="image-20210516194033120" /&gt;&lt;/p&gt; &lt;p&gt;https://dev.aliyun.com/search.html&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt;创建仓库镜像&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194114945.png" alt="image-20210516194114945" title="image-20210516194114945" /&gt;&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt; &lt;p&gt;将镜像推送到registry&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194233113a9cde3b05157efaf.png" alt="image-20210516194233113" title="image-20210516194233113" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;公有云可以查询到&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194312183.png" alt="image-20210516194312183" title="image-20210516194312183" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看详情&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194333146.png" alt="image-20210516194333146" title="image-20210516194333146" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;将阿里云上的镜像下载到本地&lt;/h3&gt; &lt;p&gt;下载到本地&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516194426312.png" alt="image-20210516194426312" title="image-20210516194426312" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 16 May 2021 11:45:00 GMT</pubDate>
    </item>
    <item>
      <title>Docker-容器数据卷与DockerFile解析（三）</title>
      <link>https://maruifu.cn/article/194</link>
      <content:encoded>&lt;h2&gt;Docker容器数据卷&lt;/h2&gt; &lt;h3&gt;是什么？&lt;/h3&gt; &lt;p&gt;先来看看Docker的理念：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;将运用与运行的环境打包形成容器运行 ，运行可以伴随着容器，但是我们对数据的要求希望是持久化的&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;容器之间希望有可能共享数据&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Docker容器产生的数据，如果不通过docker commit生成新的镜像，使得数据做为镜像的一部分保存下来，那么当容器删除后，数据自然也就没有了。&lt;/p&gt; &lt;p&gt;为了能保存数据在docker中我们使用卷。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;一句话：有点类似我们Redis里面的rdb和aof文件&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;能干嘛？&lt;/h3&gt; &lt;p&gt;卷就是目录或文件，存在于一个或多个容器中，由docker挂载到容器，但不属于联合文件系统，因此能够绕过Union File System提供一些用于持续存储或共享数据的特性：&lt;/p&gt; &lt;p&gt;卷的设计目的就是数据的持久化，完全独立于容器的生存周期，因此Docker不会在容器删除时删除其挂载的数据卷&lt;/p&gt; &lt;p&gt;特点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;数据卷可在容器之间共享或重用数据&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;卷中的更改可以直接生效&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据卷中的更改不会包含在镜像的更新中&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据卷的生命周期一直持续到没有容器使用它为止&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;容器的持久化&lt;/p&gt; &lt;p&gt;容器间继承+共享数据&lt;/p&gt; &lt;h3&gt;数据卷&lt;/h3&gt; &lt;h4&gt;容器内直接命令添加&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;命令&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -it -v /宿主机绝对路径目录:/容器内目录 镜像名 /bin/bash &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516171354850.png" alt="image-20210516171354850" title="image-20210516171354850" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查看数据卷是否挂载成功&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker inspect 容器ID &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516171618318.png" alt="image-20210516171618318" title="image-20210516171618318" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;容器和宿主机之间数据共享&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516171656699.png" alt="image-20210516171656699" title="image-20210516171656699" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;容器停止退出后，主机修改后数据是否同步&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516171808915.png" alt="image-20210516171808915" title="image-20210516171808915" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -it -v /宿主机绝对路径目录:/容器内目录:ro 镜像名 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516171926048.png" alt="image-20210516171926048" title="image-20210516171926048" /&gt;&lt;/p&gt; &lt;h4&gt;DockerFile添加&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;根目录下新建mydocker文件夹并进入&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可在Dockerfile中使用VOLUME指令来给镜像添加一个或多个数据卷&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;VOLUME[&amp;quot;/dataVolumeContainer&amp;quot;,&amp;quot;/dataVolumeContainer2&amp;quot;,&amp;quot;/dataVolumeContainer3&amp;quot;]&lt;/p&gt; &lt;p&gt;说明：&lt;/p&gt; &lt;p&gt;出于可移植和分享的考虑，用-v 主机目录:容器目录这种方法不能够直接在Dockerfile中实现。&lt;/p&gt; &lt;p&gt;由于宿主机目录是依赖于特定宿主机的，并不能够保证在所有的宿主机上都存在这样的特定目录。&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;File构建&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu mydocker]# pwd /mydocker [root@maruifu mydocker]# cat dockerfile2 # volume test FROM centos VOLUME [&amp;quot;/dataVolumeContainer1&amp;quot;,&amp;quot;/dataVolumeContainer2&amp;quot;] CMD echo &amp;quot;finished,--------success1&amp;quot; CMD /bin/bash &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;build后生成镜像 &lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516172322321.png" alt="image-20210516172322321" title="image-20210516172322321" /&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;获得一个新镜像zzyy/centos&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;run容器 &lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516172405689.png" alt="image-20210516172405689" title="image-20210516172405689" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;对应的主机目录地址&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516172507276.png" alt="image-20210516172507276" title="image-20210516172507276" /&gt;&lt;/p&gt; &lt;p&gt;主机对应默认地址&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516172543108.png" alt="image-20210516172543108" title="image-20210516172543108" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Docker挂载主机目录Docker访问出现cannot open directory .: Permission denied&lt;/p&gt; &lt;p&gt;解决办法：在挂载目录后多加一个--privileged=true参数即可&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;数据卷容器&lt;/h3&gt; &lt;h4&gt;是什么？&lt;/h4&gt; &lt;p&gt;命名的容器挂载数据卷，其它容器通过挂载这个(父容器)实现数据共享，挂载数据卷的容器，称之为数据卷容器&lt;/p&gt; &lt;h4&gt;总体介绍&lt;/h4&gt; &lt;p&gt;以上一步新建的镜像zzyy/centos为模板并运行容器dc01/dc02/dc03&lt;/p&gt; &lt;p&gt;它们已经具有容器卷 /dataVolumeContainer1 和 /dataVolumeContainer2&lt;/p&gt; &lt;h4&gt;容器间传递共享&lt;/h4&gt; &lt;ol&gt; &lt;li&gt;先启动一个父容器dc01&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516172927157.png" alt="image-20210516172927157" title="image-20210516172927157" /&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt; &lt;p&gt;在dataVolumeContainer2新增内容&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;dc02/dc03继承自dc01&lt;/p&gt; &lt;p&gt;&lt;code&gt;--volumes-from&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;code&gt;docker run -it --name dc02 --volumes-from dc01 zzyy/centos&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516173120377.png" alt="image-20210516173120377" title="image-20210516173120377" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;dc02/dc03分别在dataVolumeContainer2各自新增内容&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;回到dc01可以看到02/03各自添加的都能共享了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516173232318.png" alt="image-20210516173232318" title="image-20210516173232318" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;删除dc01，dc02修改后dc03可否访问&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516173325782.png" alt="image-20210516173325782" title="image-20210516173325782" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;新建dc04继承dc03后再删除dc03&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516173358106.png" alt="image-20210516173358106" title="image-20210516173358106" /&gt;&lt;/p&gt; &lt;p&gt;结论：容器之间配置信息的传递，数据卷的生命周期一直持续到没有容器使用它为止&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;DockerFile解析&lt;/h2&gt; &lt;h3&gt;是什么？&lt;/h3&gt; &lt;p&gt;Dockerfile是用来构建Docker镜像的构建文件，是由一系列命令和参数构成的脚本。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;构建三步骤&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;编写Dockerfile文件  -&amp;gt;docker build-&amp;gt;docker run&lt;/p&gt; &lt;p&gt;样例：以我们熟悉的CentOS为例&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516173727989.png" alt="image-20210516173727989" title="image-20210516173727989" /&gt;&lt;/p&gt; &lt;h3&gt;DockerFile构建过程解析&lt;/h3&gt; &lt;h4&gt;Dockerfile内容基础知识&lt;/h4&gt; &lt;p&gt;1：每条保留字指令都必须为大写字母且后面要跟随至少一个参数&lt;/p&gt; &lt;p&gt;2：指令按照从上到下，顺序执行&lt;/p&gt; &lt;p&gt;3：#表示注释&lt;/p&gt; &lt;p&gt;4：每条指令都会创建一个新的镜像层，并对镜像进行提交&lt;/p&gt; &lt;h4&gt;Docker执行Dockerfile的大致流程&lt;/h4&gt; &lt;p&gt;（1）docker从基础镜像运行一个容器&lt;/p&gt; &lt;p&gt;（2）执行一条指令并对容器作出修改&lt;/p&gt; &lt;p&gt;（3）执行类似docker commit的操作提交一个新的镜像层&lt;/p&gt; &lt;p&gt;（4）docker再基于刚提交的镜像运行一个新容器&lt;/p&gt; &lt;p&gt;（5）执行dockerfile中的下一条指令直到所有指令都执行完成&lt;/p&gt; &lt;h4&gt;总结&lt;/h4&gt; &lt;p&gt;从应用软件的角度来看，Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段，&lt;/p&gt; &lt;p&gt;*  Dockerfile是软件的原材料&lt;/p&gt; &lt;p&gt;*  Docker镜像是软件的交付品&lt;/p&gt; &lt;p&gt;*  Docker容器则可以认为是软件的运行态。&lt;/p&gt; &lt;p&gt;Dockerfile面向开发，Docker镜像成为交付标准，Docker容器则涉及部署与运维，三者缺一不可，合力充当Docker体系的基石。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516174021704.png" alt="image-20210516174021704" title="image-20210516174021704" /&gt;&lt;/p&gt; &lt;p&gt;Dockerfile，需要定义一个Dockerfile，Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道，这时需要考虑如何设计namespace的权限控制)等等;&lt;/p&gt; &lt;p&gt;Docker镜像，在用Dockerfile定义一个文件之后，docker build时会产生一个Docker镜像，当运行 Docker镜像时，会真正开始提供服务;&lt;/p&gt; &lt;p&gt;Docker容器，容器是直接提供服务的。&lt;/p&gt; &lt;h3&gt;DockerFile体系结构(保留字指令）&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;FROM       #基础镜像，当前新镜像是基于哪个镜像的 MAINTAINER #镜像维护者的姓名和邮箱地址 RUN        #容器构建时需要运行的命令 EXPOSE     #容器构建时需要运行的命令 WOEKDIR    #指定在创建容器后，终端默认登陆的进来工作目录，一个落脚点 ENV        #用来在构建镜像过程中设置环境变量             #ENV MY_PATH /usr/mytest             #这个环境变量可以在后续的任何RUN指令中使用，这就如同在命令前面指定了环境变量前缀一样；             #也可以在其它指令中直接使用这些环境变量，             #比如：WORKDIR $MY_PATH ADD        #将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包 COPY       #类似ADD，拷贝文件和目录到镜像中。将从构建上下文目录中 &amp;lt;源路径&amp;gt; 的文件/目录复制到新的一层的镜像内的 &amp;lt;目标路径&amp;gt; 位置       #COPY src dest       #COPY [&amp;quot;src&amp;quot;, &amp;quot;dest&amp;quot;] VOLUME     #容器数据卷，用于数据保存和持久化工作    CMD        #指定一个容器启动时要运行的命令       #CMD指令的格式和RUN相似，也是两种格式       #shell格式：CMD&amp;lt;命令&amp;gt;       #exec格式：CMD[&amp;quot;可执行文件&amp;quot;,&amp;quot;参数1&amp;quot;,&amp;quot;参数2&amp;quot;...]       #参数列表格式：CMD[&amp;quot;参数1&amp;quot;,&amp;quot;参数2&amp;quot;...]。在指定了ENTRYPOINT指令后，用CMD指定具体的参数。  ENTRYPOINT #指定一个容器启动时要运行的命令  ，ENTRYPOINT的目的和 CMD 一样都是在指定容器启动程序及参数        ONBUILD    # 当构建一个被继承的Dockerfile时运行命令，父镜像在被子继承后父镜像的onbuild被触发    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516175406219.png" alt="image-20210516175406219" title="image-20210516175406219" /&gt;&lt;/p&gt; &lt;p&gt;总结：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516175434617.png" alt="image-20210516175434617" title="image-20210516175434617" /&gt;&lt;/p&gt; &lt;h3&gt;案例&lt;/h3&gt; &lt;h4&gt;Base镜像(scratch)&lt;/h4&gt; &lt;p&gt;Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516175956793.png" alt="image-20210516175956793" title="image-20210516175956793" /&gt;&lt;/p&gt; &lt;h4&gt;自定义镜像mycentos&lt;/h4&gt; &lt;h5&gt;编写&lt;/h5&gt; &lt;p&gt;1.Hub默认CentOS镜像什么情况&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516180146733.png" alt="image-20210516180146733" title="image-20210516180146733" /&gt;&lt;/p&gt; &lt;p&gt;自定义mycentos目的使我们自己的镜像具备如下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;​     登陆后的默认路径&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;​     vim编辑器&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;​     查看网络配置ifconfig支持&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;2.准备编写DockerFile文件&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516180248761.png" alt="image-20210516180248761" title="image-20210516180248761" /&gt;&lt;/p&gt; &lt;p&gt;3.myCentOS内容DockerFile&lt;/p&gt; &lt;pre&gt;&lt;code&gt;FROM centos MAINTAINER zzyy&amp;lt;zzyy167@126.com&amp;gt;  ENV MYPATH /usr/local WORKDIR $MYPATH  RUN yum -y install vim RUN yum -y install net-tools  EXPOSE 80  CMD echo $MYPATH CMD echo &amp;quot;success--------------ok&amp;quot; CMD /bin/bash &lt;/code&gt;&lt;/pre&gt; &lt;h5&gt;构建&lt;/h5&gt; &lt;pre&gt;&lt;code&gt;docker build -t 新镜像名字:TAG . &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;会看到 docker build 命令最后有一个 .&lt;/p&gt; &lt;p&gt;. 表示当前目录&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516180454777.png" alt="image-20210516180454777" title="image-20210516180454777" /&gt;&lt;/p&gt; &lt;h5&gt;运行&lt;/h5&gt; &lt;pre&gt;&lt;code&gt;docker run -it 新镜像名字 :TAG &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516180642989.png" alt="image-20210516180642989" title="image-20210516180642989" /&gt;&lt;/p&gt; &lt;p&gt;可以看到，我们自己的新镜像已经支持vim/ifconfig命令，扩展成功了。&lt;/p&gt; &lt;h5&gt;变更历史&lt;/h5&gt; &lt;pre&gt;&lt;code&gt;docker history 镜像名 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;CMD/ENTRYPOINT 镜像&lt;/h4&gt; &lt;p&gt;都是指定一个容器启动时要运行的命令&lt;/p&gt; &lt;p&gt;CMD:Dockerfile 中可以有多个 CMD 指令，但只有最后一个生效，CMD 会被 docker run 之后的参数替换&lt;/p&gt; &lt;p&gt;ENTRYPOINT:docker run 之后的参数会被当做参数传递给 ENTRYPOINT，之后形成新的命令组合&lt;/p&gt; &lt;h4&gt;自定义镜像Tomcat9&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;code&gt;mkdir -p /maruifu/mydockerfile/tomcat9&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在上述目录下touch c.txt&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;将jdk和tomcat安装的压缩包拷贝进上一步目录&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cp apache-tomcat-9.0.8.tar.gz /maruifu/mydockerfile/tomcat9 cp jdk-8u171-linux-x64.tar.gz /maruifu/mydockerfile/tomcat9 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在/maruifu/mydockerfile/tomcat9目录下新建Dockerfile文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;FROM         centos MAINTAINER    zzyy&amp;lt;zzyybs@126.com&amp;gt; #把宿主机当前上下文的c.txt拷贝到容器/usr/local/路径下 COPY c.txt /usr/local/cincontainer.txt #把java与tomcat添加到容器中 ADD jdk-8u171-linux-x64.tar.gz /usr/local/ ADD apache-tomcat-9.0.8.tar.gz /usr/local/ #安装vim编辑器 RUN yum -y install vim #设置工作访问时候的WORKDIR路径，登录落脚点 ENV MYPATH /usr/local WORKDIR $MYPATH #配置java与tomcat环境变量 ENV JAVA_HOME /usr/local/jdk1.8.0_171 ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.8 ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.8 ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin #容器运行时监听的端口 EXPOSE  8080 #启动时运行tomcat # ENTRYPOINT [&amp;quot;/usr/local/apache-tomcat-9.0.8/bin/startup.sh&amp;quot; ] # CMD [&amp;quot;/usr/local/apache-tomcat-9.0.8/bin/catalina.sh&amp;quot;,&amp;quot;run&amp;quot;] CMD /usr/local/apache-tomcat-9.0.8/bin/startup.sh &amp;amp;&amp;amp; tail -F /usr/local/apache-tomcat-9.0.8/bin/logs/catalina.out  &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;构建 &lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516181902542.png" alt="image-20210516181902542" title="image-20210516181902542" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;构建完成：docker image&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182025388.png" alt="image-20210516182025388" title="image-20210516182025388" /&gt;&lt;/p&gt; &lt;ol start="6"&gt; &lt;li&gt; &lt;p&gt;run&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -d -p 9080:8080 --name myt9 -v /zzyyuse/mydockerfile/tomcat9/test:/usr/local/apache-tomcat-9.0.8/webapps/test -v /zzyyuse/mydockerfile/tomcat9/tomcat9logs/:/usr/local/apache-tomcat-9.0.8/logs --privileged=true zzyytomcat9 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182108177.png" alt="image-20210516182108177" title="image-20210516182108177" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;Docker挂载主机目录Docker访问出现cannot open directory .: Permission denied&lt;/p&gt; &lt;p&gt;解决办法：在挂载目录后多加一个--privileged=true参数即可&lt;/p&gt; &lt;/blockquote&gt; &lt;ol start="7"&gt; &lt;li&gt; &lt;p&gt;验证 &lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182158450.png" alt="image-20210516182158450" title="image-20210516182158450" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;结合前述的容器卷将测试的web服务test发布&lt;/p&gt; &lt;p&gt;总体概述&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182411183.png" alt="image-20210516182411183" title="image-20210516182411183" /&gt;&lt;/p&gt; &lt;p&gt;web.xml&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;web-app xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;   xmlns=&amp;quot;http://java.sun.com/xml/ns/javaee&amp;quot;   xsi:schemaLocation=&amp;quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd&amp;quot;   id=&amp;quot;WebApp_ID&amp;quot; version=&amp;quot;2.5&amp;quot;&amp;gt;   &amp;lt;display-name&amp;gt;test&amp;lt;/display-name&amp;gt; &amp;lt;/web-app&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;a.jsp&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;%@ page language=&amp;quot;java&amp;quot; contentType=&amp;quot;text/html; charset=UTF-8&amp;quot; pageEncoding=&amp;quot;UTF-8&amp;quot;%&amp;gt; &amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD HTML 4.01 Transitional//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/loose.dtd&amp;quot;&amp;gt; &amp;lt;html&amp;gt;   &amp;lt;head&amp;gt;     &amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=UTF-8&amp;quot;&amp;gt;     &amp;lt;title&amp;gt;Insert title here&amp;lt;/title&amp;gt;   &amp;lt;/head&amp;gt;   &amp;lt;body&amp;gt;     -----------welcome------------     &amp;lt;%=&amp;quot;i am in docker tomcat self &amp;quot;%&amp;gt;     &amp;lt;br&amp;gt;     &amp;lt;br&amp;gt;     &amp;lt;% System.out.println(&amp;quot;=============docker tomcat self&amp;quot;);%&amp;gt;   &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;测试&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182604605.png" alt="image-20210516182604605" title="image-20210516182604605" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;总结&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516182635512.png" alt="image-20210516182635512" title="image-20210516182635512" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 16 May 2021 10:27:00 GMT</pubDate>
    </item>
    <item>
      <title>Docker-命令与镜像（二）</title>
      <link>https://maruifu.cn/article/193</link>
      <content:encoded>&lt;h2&gt;Docker常用命令&lt;/h2&gt; &lt;h3&gt;帮助命令&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;docker version docker info docker --help &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;镜像命令&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;列出本地主机上的镜像&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker images #各个选项说明: #    - REPOSITORY：表示镜像的仓库源 #    - TAG：镜像的标签 #    - IMAGE ID：镜像ID #    - CREATED：镜像创建时间 #    - SIZE：镜像大小 #同一仓库源可以有多个 TAG，代表这个仓库源的不同个版本，我们使用 REPOSITORY:TAG 来定义不同的镜像。 #如果你不指定一个镜像的版本标签，例如你只使用 ubuntu，docker 将默认使用 ubuntu:latest 镜像  #OPTIONS说明 #   - -a :列出本地所有的镜像（含中间映像层） #   - -q :只显示镜像ID。 #   - --digests :显示镜像的摘要信息 #   - --no-trunc :显示完整的镜像信息 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;docker search 某个XXX镜像名字&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#地址：https://hub.docker.com docker search [OPTIONS] 镜像名字 # OPTIONS说明： #    --no-trunc : 显示完整的镜像描述 #    -s : 列出收藏数不小于指定值的镜像。 #    --automated : 只列出 automated build类型的镜像； &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;docker pull 某个XXX镜像名字&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#下载镜像 docker pull 镜像名字 [:TAG] &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;docker rmi 某个XXX镜像名字ID&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#删除单个 docker rmi  -f 镜像ID #删除多个 docker rmi -f 镜像名1:TAG 镜像名2:TAG #删除全部 docker rmi -f $(docker images -qa) &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;容器命令&lt;/h3&gt; &lt;p&gt;有镜像才能创建容器，这是根本前提(下载一个CentOS镜像演示)&lt;/p&gt; &lt;p&gt;&lt;code&gt;docker pull centos&lt;/code&gt;&lt;/p&gt; &lt;h4&gt;新建并启动容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker run [OPTIONS] IMAGE [COMMAND][ARG...]  # OPTIONS说明（常用）：有些是一个减号，有些是两个减号 #--name=&amp;quot;容器新名字&amp;quot;: 为容器指定一个名称； #-d: 后台运行容器，并返回容器ID，也即启动守护式容器； #-i：以交互模式运行容器，通常与 -t 同时使用； #-t：为容器重新分配一个伪输入终端，通常与 -i 同时使用； #-P: 随机端口映射； #-p: 指定端口映射，有以下四种格式 #      ip:hostPort:containerPort #      ip::containerPort #      hostPort:containerPort #      containerPort  #使用镜像centos:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。 docker run -it centos /bin/bash  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;列出当前所有正在运行的容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker ps [OPTIONS] # OPTIONS说明（常用）： #  -a :列出当前所有正在运行的容器+历史上运行过的 #  -l :显示最近创建的容器。 #  -n：显示最近n个创建的容器。 #  -q :静默模式，只显示容器编号。 #  --no-trunc :不截断输出。 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;退出容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;#两种退出方式 #容器停止退出 exit #容器不停止退出 ctrl+P+Q &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;启动容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker start 容器ID或者容器名 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;重启容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker restart 容器ID或者容器名 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;停止容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker stop 容器ID或者容器名 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;强制停止容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker kill 容器ID或者容器名 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;删除已停止容器&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;# 删除单个容器 docker rm 容器ID #一次删除多个容器 docker rm -f $(docker ps -a -q) docker ps -a -q | xargs docker rm &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;重要&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;启动守护式容器&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#启动守护式容器 docker run -d 容器名 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用镜像centos:latest以后台模式启动一个容器&lt;code&gt;docker run -d centos&lt;/code&gt;&lt;/p&gt; &lt;p&gt;问题：然后docker ps -a 进行查看, 会发现容器已经退出很重要的要说明的一点: Docker容器后台运行,就必须有一个前台进程.容器运行的命令如果不是那些一直挂起的命令（比如运行top，tail），就是会自动退出的。&lt;/p&gt; &lt;p&gt;这个是docker的机制问题,比如你的web容器,我们以nginx为例，正常情况下,我们配置启动服务只需要启动响应的service即可。例如&lt;code&gt;service nginx start&lt;/code&gt;但是,这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀因为他觉得他没事可做了.所以，最佳的解决方案是,将你要运行的程序以前台进程的形式运行&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看容器日志&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker logs -f -t --tail 容器ID  docker run -d centos /bin/sh -c &amp;quot;while true;do echo hello zzyy;sleep 2;done&amp;quot; #   -t 是加入时间戳 #   -f 跟随最新的日志打印 #   --tail 数字 显示最后多少条 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看容器内运行的进程&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker top 容器ID &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;查看容器内部细节&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker inspect 容器ID &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;进入正在运行的容器并以命令行交互&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#直接进入 docker exec -it 容器ID bash #重新进入 docker attach 容器ID #区别 exec是在容器中打开新的终端，并且可以启动新的进程 attach直接进入容器启动命令的终端，不会启动新的进程 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;从容器内拷贝文件到主机上&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker cp  容器ID:容器内路径 目的主机路径 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516165028676.png" alt="image-20210516165028676" title="image-20210516165028676" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;总结&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516165208563.png" alt="image-20210516165208563" title="image-20210516165208563" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;attach    Attach to a running container                 # 当前 shell 下 attach 连接指定运行镜像 build     Build an image from a Dockerfile              # 通过 Dockerfile 定制镜像 commit    Create a new image from a container changes   # 提交当前容器为新的镜像 cp        Copy files/folders from the containers filesystem to the host path   #从容器中拷贝指定文件或者目录到宿主机中 create    Create a new container                        # 创建一个新的容器，同 run，但不启动容器 diff      Inspect changes on a container's filesystem   # 查看 docker 容器变化 events    Get real time events from the server          # 从 docker 服务获取容器实时事件 exec      Run a command in an existing container        # 在已存在的容器上运行命令 export    Stream the contents of a container as a tar archive   # 导出容器的内容流作为一个 tar 归档文件[对应 import ] history   Show the history of an image                  # 展示一个镜像形成历史 images    List images                                   # 列出系统当前镜像 import    Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应export] info      Display system-wide information               # 显示系统相关信息 inspect   Return low-level information on a container   # 查看容器详细信息 kill      Kill a running container                      # kill 指定 docker 容器 load      Load an image from a tar archive              # 从一个 tar 包中加载一个镜像[对应 save] login     Register or Login to the docker registry server    # 注册或者登陆一个 docker 源服务器 logout    Log out from a Docker registry server          # 从当前 Docker registry 退出 logs      Fetch the logs of a container                 # 输出当前容器日志信息 port      Lookup the public-facing port which is NAT-ed to PRIVATE_PORT    # 查看映射端口对应的容器内部源端口 pause     Pause all processes within a container        # 暂停容器 ps        List containers                               # 列出容器列表 pull      Pull an image or a repository from the docker registry server   # 从docker镜像源服务器拉取指定镜像或者库镜像 push      Push an image or a repository to the docker registry server    # 推送指定镜像或者库镜像至docker源服务器 restart   Restart a running container                   # 重启运行的容器 rm        Remove one or more containers                 # 移除一个或者多个容器 rmi       Remove one or more images             # 移除一个或多个镜像[无容器使用该镜像才可删除，否则需删除相关容器才可继续或 -f 强制删除] run       Run a command in a new container              # 创建一个新的容器并运行一个命令 save      Save an image to a tar archive                # 保存一个镜像为一个 tar 包[对应 load] search    Search for an image on the Docker Hub         # 在 docker hub 中搜索镜像 start     Start a stopped containers                    # 启动容器 stop      Stop a running containers                     # 停止容器 tag       Tag an image into a repository                # 给源中镜像打标签 top       Lookup the running processes of a container   # 查看容器中运行的进程信息 unpause   Unpause a paused container                    # 取消暂停容器 version   Show the docker version information           # 查看 docker 版本号 wait      Block until a container stops, then print its exit code   # 截取容器停止时的退出状态值 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Docker镜像&lt;/h2&gt; &lt;h3&gt;是什么？&lt;/h3&gt; &lt;p&gt;镜像是一种轻量级、可执行的独立软件包，用来打包软件运行环境和基于运行环境开发的软件，它包含运行某个软件所需的所有内容，包括代码、运行时、库、环境变量和配置文件。&lt;/p&gt; &lt;h4&gt;UnionFS（联合文件系统）&lt;/h4&gt; &lt;p&gt;UnionFS（联合文件系统）：Union文件系统（UnionFS）是一种分层、轻量级并且高性能的文件系统，它支持对文件系统的修改作为一次提交来一层层的叠加，同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承，基于基础镜像（没有父镜像），可以制作各种具体的应用镜像。&lt;/p&gt; &lt;p&gt;特性：一次同时加载多个文件系统，但从外面看起来，只能看到一个文件系统，联合加载会把各层文件系统叠加起来，这样最终的文件系统会包含所有底层的文件和目录&lt;/p&gt; &lt;h4&gt;Docker镜像加载原理&lt;/h4&gt; &lt;p&gt;Docker镜像加载原理：docker的镜像实际上由一层一层的文件系统组成，这种层级的文件系统UnionFS。&lt;/p&gt; &lt;p&gt;bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统，在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的，包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了，此时内存的使用权已由bootfs转交给内核，此时系统也会卸载bootfs。&lt;/p&gt; &lt;p&gt;rootfs (root file system) ，在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版，比如Ubuntu，Centos等等。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516165625576.png" alt="image-20210516165625576" title="image-20210516165625576" /&gt;&lt;/p&gt; &lt;p&gt;平时我们安装进虚拟机的CentOS都是好几个G，为什么docker这里才200M？？&lt;/p&gt; &lt;p&gt;对于一个精简的OS，rootfs可以很小，只需要包括最基本的命令、工具和程序库就可以了，因为底层直接用Host的kernel，自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。&lt;/p&gt; &lt;h4&gt;分层的镜像&lt;/h4&gt; &lt;p&gt;以我们的pull为例，在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516165726466.png" alt="image-20210516165726466" title="image-20210516165726466" /&gt;&lt;/p&gt; &lt;h4&gt;Docker 镜像为啥采用分层结构&lt;/h4&gt; &lt;p&gt;最大的一个好处就是 - 共享资源&lt;/p&gt; &lt;p&gt;比如：有多个镜像都从相同的 base 镜像构建而来，那么宿主机只需在磁盘上保存一份base镜像，同时内存中也只需加载一份 base 镜像，就可以为所有容器服务了。而且镜像的每一层都可以被共享。&lt;/p&gt; &lt;h3&gt;特点&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Docker镜像都是只读的&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;当容器启动时，一个新的可写层被加载到镜像的顶部。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;这一层通常被称作“容器层”，“容器层”之下的都叫“镜像层”。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Docker镜像commit操作补充&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;docker commit提交容器副本使之成为一个新的镜像  docker commit -m=“提交的描述信息” -a=“作者” 容器ID 要创建的目标镜像名:[标签名] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;案例演示&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;从Hub上下载tomcat镜像到本地并成功运行&lt;/p&gt; &lt;pre&gt;&lt;code&gt;docker run -it -p 8080:8080 tomcat #  -p 主机端口:docker容器端口 #  -P 随机分配端口 #  i:交互 #  t:终端 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516170235021.png" alt="image-20210516170235021" title="image-20210516170235021" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;故意删除上一步镜像生产tomcat容器的文档&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516170357529.png" alt="image-20210516170357529" title="image-20210516170357529" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;也即当前的tomcat运行实例是一个没有文档内容的容器，以它为模板commit一个没有doc的tomcat新镜像atguigu/tomcat02&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516170424932.png" alt="image-20210516170424932" title="image-20210516170424932" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;启动我们的新镜像并和原来的对比&lt;/p&gt; &lt;p&gt;启动atguigu/tomcat02，它没有docs&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516170556487.png" alt="image-20210516170556487" title="image-20210516170556487" /&gt;&lt;/p&gt; &lt;p&gt;新启动原来的tomcat，它有docs&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516170537899.png" alt="image-20210516170537899" title="image-20210516170537899" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Sun, 16 May 2021 09:07:00 GMT</pubDate>
    </item>
    <item>
      <title>Docker-简介与安装（一）</title>
      <link>https://maruifu.cn/article/192</link>
      <content:encoded>&lt;h2&gt;Docker简介&lt;/h2&gt; &lt;h3&gt;是什么？&lt;/h3&gt; &lt;h4&gt;为什么会有docker？&lt;/h4&gt; &lt;p&gt;一款产品从开发到上线，从操作系统，到运行环境，再到应用配置。作为开发+运维之间的协作我们需要关心很多东西，这也是很多互联网公司都不得不面对的问题，特别是各种版本的迭代之后，不同版本环境的兼容，对运维人员都是考验&lt;/p&gt; &lt;p&gt;Docker之所以发展如此迅速，也是因为它对此给出了一个标准化的解决方案。&lt;/p&gt; &lt;p&gt;环境配置如此麻烦，换一台机器，就要重来一次，费力费时。很多人想到，能不能从根本上解决问题，软件可以带环境安装？也就是说，安装的时候，把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150107135.png" alt="image-20210516150107135" title="image-20210516150107135" /&gt;&lt;/p&gt; &lt;p&gt;之前在服务器配置一个应用的运行环境，要安装各种软件，就拿尚硅谷电商项目的环境来说吧，Java/Tomcat/MySQL/JDBC驱动包等。安装和配置这些东西有多麻烦就不说了，它还不能跨平台。假如我们是在 Windows 上安装的这些环境，到了 Linux 又得重新装。况且就算不跨操作系统，换另一台同样操作系统的服务器，要移植应用也是非常麻烦的。&lt;/p&gt; &lt;p&gt;传统上认为，软件编码开发/测试结束后，所产出的成果即是程序或是能够编译执行的二进制字节码等(java为例)。而为了让这些程序可以顺利执行，开发团队也得准备完整的部署文件，让维运团队得以部署应用程式，开发需要清楚的告诉运维部署团队，用的全部配置文件+所有软件环境。不过，即便如此，仍然常常发生部署失败的状况。Docker镜像的设计，使得Docker得以打破过去「程序即应用」的观念。透过镜像(images)将作业系统核心除外，运作应用程式所需要的系统环境，由下而上打包，达到应用程式跨平台间的无缝接轨运作。&lt;/p&gt; &lt;h4&gt;docker理念？&lt;/h4&gt; &lt;p&gt;Docker是基于Go语言实现的云开源项目。&lt;/p&gt; &lt;p&gt;Docker的主要目标是“Build，Ship and Run Any App,Anywhere”，也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理，使用户的APP（可以是一个WEB应用或数据库应用等等）及其运行环境能够做到“一次封装，到处运行”。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150211066.png" alt="image-20210516150211066" title="image-20210516150211066" /&gt;&lt;/p&gt; &lt;p&gt;Linux 容器技术的出现就解决了这样一个问题，而 Docker 就是在它的基础上发展过来的。将应用运行在 Docker 容器上面，而 Docker 容器在任何操作系统上都是一致的，这就实现了跨平台、跨服务器。只需要一次配置好环境，换到别的机子上就可以一键部署好，大大简化了操作&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;一句话解决了运行环境和配置问题软件容器，方便做持续集成并有助于整体发布的容器虚拟化技术。&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;能干嘛？&lt;/h3&gt; &lt;h4&gt;之前的虚拟机技术&lt;/h4&gt; &lt;p&gt;虚拟机（virtual machine）就是带环境安装的一种解决方案。&lt;/p&gt; &lt;p&gt;它可以在一种操作系统里面运行另一种操作系统，比如在Windows 系统里面运行Linux 系统。应用程序对此毫无感知，因为虚拟机看上去跟真实系统一模一样，而对于底层系统来说，虚拟机就是一个普通文件，不需要了就删掉，对其他部分毫无影响。这类虚拟机完美的运行了另一套系统，能够使应用程序，操作系统和硬件三者之间的逻辑不变。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150346488bb749b9f50392824.png" alt="image-20210516150346488" title="image-20210516150346488" /&gt; 虚拟机的缺点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;资源占用多&lt;/li&gt; &lt;li&gt;冗余步骤多&lt;/li&gt; &lt;li&gt;启动慢&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;容器虚拟化技术&lt;/h4&gt; &lt;p&gt;由于前面虚拟机存在这些缺点，Linux 发展出了另一种虚拟化技术：Linux 容器（Linux Containers，缩写为 LXC）。&lt;/p&gt; &lt;p&gt;Linux 容器不是模拟一个完整的操作系统，而是对进程进行隔离。有了容器，就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同，不需要捆绑一整套操作系统，只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一的运行&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150502633.png" alt="image-20210516150502633" title="image-20210516150502633" /&gt;&lt;/p&gt; &lt;p&gt;比较了 Docker 和传统虚拟化方式的不同之处：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；&lt;/li&gt; &lt;li&gt;而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。&lt;/li&gt; &lt;li&gt;每个容器之间互相隔离，每个容器有自己的文件系统 ，容器之间进程不会相互影响，能区分计算资源。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;开发/运维(DevOps)&lt;/h4&gt; &lt;p&gt;一次构建，随处运行&lt;/p&gt; &lt;ol&gt; &lt;li&gt;更快速的应用交付和部署&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;传统的应用开发完成后，需要提供一堆安装程序和配置说明文档，安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件，在正式生产环境加载镜像并运行即可，应用安装配置在镜像里已经内置好，大大节省部署配置和测试验证时间。&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;更便捷的升级和扩缩容&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;随着微服务架构和Docker的发展，大量的应用会通过微服务方式架构，应用的开发构建将变成搭乐高积木一样，每个Docker容器将变成一块“积木”，应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时，可通过镜像运行新的容器进行快速扩容，使应用系统的扩容从原先的天级变成分钟级甚至秒级。&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt;更简单的系统运维&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;应用容器化运行后，生产环境运行的应用可与开发、测试环境的应用高度一致，容器会将应用程序相关的环境和状态完全封装起来，不会因为底层基础架构和操作系统的不一致性给应用带来影响，产生新的BUG。当出现程序异常时，也可以通过测试环境的相同容器进行快速定位和修复。&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;更高效的计算资源利用&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;　Docker是内核级虚拟化，其不像传统的虚拟化技术一样需要额外的Hypervisor支持，所以在一台物理机上可以运行很多个容器实例，可大大提升物理服务器的CPU和内存的利用率。&lt;/p&gt; &lt;h4&gt;企业级&lt;/h4&gt; &lt;p&gt;新浪&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150938180.png" alt="image-20210516150938180" title="image-20210516150938180" /&gt;&lt;/p&gt; &lt;p&gt;美团&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516150954168.png" alt="image-20210516150954168" title="image-20210516150954168" /&gt;&lt;/p&gt; &lt;p&gt;蘑菇街&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516151009037.png" alt="image-20210516151009037" title="image-20210516151009037" /&gt;&lt;/p&gt; &lt;h3&gt;去那下？&lt;/h3&gt; &lt;h4&gt;官网&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;docker官网：http://www.docker.com docker中文网站：https://www.docker-cn.com/ &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;仓库&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;Docker Hub官网: https://hub.docker.com/ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Docker安装&lt;/h2&gt; &lt;h3&gt;前提说明&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;CentOS Docker 安装&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Docker支持以下的CentOS版本：&lt;/p&gt; &lt;p&gt;CentOS 7 (64-bit)&lt;/p&gt; &lt;p&gt;CentOS 6.5 (64-bit) 或更高的版本&lt;/p&gt; &lt;p&gt;&lt;strong&gt;前提条件&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;目前，CentOS 仅发行版本中的内核支持 Docker。&lt;/p&gt; &lt;p&gt;Docker 运行在 CentOS 7 上，要求系统为64位、系统内核版本为 3.10 以上。&lt;/p&gt; &lt;p&gt;Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上，要求系统为64位、系统内核版本为 2.6.32-431 或者更高版本。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查看自己的内核&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;uname命令用于打印当前系统相关信息（内核版本号、硬件架构、主机名称和操作系统类型等）。&lt;/p&gt; &lt;p&gt;&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAhwAAABwCAIAAADaNT4jAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABZGSURBVHhe7d35TxRn/Afw/h1fWK+CYA2KFPA+QGMrRPlBRdQaq8bWemBV1GjqUTziL9UY8YjFpp6t4oHxwCOiwFer0uKVaMBYUbG1NV88gtZQ637f7vPsw7Cz8+ywM3RL+37lCZnnnNk5ns/MLrDv3Kj5P5W+/fbbzz777GHdfSYmJiYmpjBSsKDy4D4TExMTE1MYKUhQ+d/ys0xMTExMTGGkIEHFS0REFBYGFSIicg2DChERucYyqPyPn8j+o8gt+0duG0WKPCe0Z4Wq1TcjorDpgopYiIiSkpIdO3bITHPGDQtjIzUjO5ednT1hwgSZIa0jR4706dPH4/HgIBYXF8tSv7D3pP6UULVhnDlEZMc/NKjMnj17yJAhMmNg3qqWbqfVyK7Iy8tbtGiRzJC1xsbG2NjYqVOnVlZWXr169enTp7LCL+w9qT8fVG1kT2+if7G2GlSwIJZViU2tGlTIprq6Ohy4Y8eOybx7rM6Ht6eLiawjIveEH1Ru376NNpgXli9f3q1bt3bt2qWnp798+VLUlpeXZ2RkdOzYsUuXLtOnT//tt99EuaCp7dChg+96b0Y1wLL6KRbUMtTW1ubm5qampmJjEhISZs2a9ejRI1kXamQoKirq3bs3mo0cOXLPnj2ovXXrlqjasGEDysUy4OYatRcuXJB5r1e8jQNB37RxMrIe9mRmZib2JAYZPHgwViTKX7x4ERcXt3LlSpEV8GTQv39/mbHuq+zcuRMBGLXx8fFTpky5f/++rNAeQfGKzp49i/Ohffv2AwcOvHz5sqzz1fr2UzPGt7/C3pMCSuSSiarStAlKf4z0r1dzTt69exfjLFiwAA9tI0aMwKXUvXv3xMTEH3/8UTSA6urqjz/+GIfy3Xff/eijj9BFVjimv36JwuY0qAwaNGjhwoU3btzAtb1u3bqGhgZUXbt2DefouHHjSktL9+/f36NHD5yvf/75p+ior33w4AEmL0x/uDixILx+/VrUiq1S24YFtQwVFRXz588/dOgQLvijR49i1ktLS7M5MiaFqKgoXP+YLgsKCjBXYmT7U78YPCsryzwVOhxZA1M5pvWJEyeWlZWhy9atW9evXy/rvF4cF8xQ6gXW19djytu0aZPI6vtCfn5+dHT0smXL0ODEiRMzZ87ELhVV+iOIV4SOOTk5d+7cwRMJZv+UlJS//vpL1D5//hw76tKlS3iZCFq+g3AfIVDUQth7UkCJXDJRVZo2QYUMKprXqzknRVDBo/Phw4cRSnv27In9iTA/efJk0RcDIpx8+OGHx48fP336NPZJcnIydqCodUhz/RI54TSo4O5J5g1wO9a1a1d113PmzBm0PHXqlMjqawX9219q27Cgls3ExV9VVSXzPlYjZ2dnY2Z88+aNyGIiQN+WTv1BP152ZeSgzp07h8boIvPNYaZArdqxhYWFiASPHz8WWX1fzOyYKNesWSPzPips6I8gXhGy6gViNkS2pqZGZAWEDRSWlJTIvEkYe1JAiVwyUVWaNkGFDCrGzQj6ehXRV5yTIqggvmIZT5B4ZMECQvuAAQPeNvV6P/30U+xn9YHTkydP8LzyzTffiKweRg4gK/w01y+RE06Dyp49e2TeoF+/fupuCxobG40zlL5WCC+o4K588+bN6Ij7O8wCuDFHrc1wFRMTg7tymfF6cfOIvq4EFVdGDurhw4eIE7g1xp2s8Y0+ZdiwYWpXDx06FE9pYhn0fffu3YvNwJ2yzDenP4J4RbjvVlP/lStXMNTFixdFVggvqOj3pIASuWSiqjRtggoZVDSvV3NOiqAith9PMOK9SsT+pKSktz293vj4+M8//1wsCyNGjMAjo8w4o7l+iZxwGlQCpmyhe/fuc+bMkRmf2NjYxYsXi2V9rWAnqBh/CqtXr8ZEWVBQcP36dWxeaWkpagNmrqAj4x4cLdeuXSvzvndaUOI8qLg1spWKiooxY8ZgtkWvvn37nj9/Xlb47Nq1Szyd3Lx5Ew2walnho+m7ZcsWFFq906I/gnZeURhBJeSeFFAil0xUlaZNUCGDiqZWc04ag0pmZuaqVauwgAcR7F4sIBqhFuEKgysI3m49W2iuXyInnAYVPOzLvEFrP6moBfxUJZCcnCzeQxDEG/d2ggro74I3btyIS1osQ2VlJWrtBBVwZWQ9TEDokp6enpCQoN7Nh4aGhs6dO+NOGRuQmppqrFKC9nX4pGJ8RW4FFQj7SQWFZrLOBv0x0r9ezTmpDyqAhxuEalxoRr/88ouodQhDYe1Br18iJ1olqAR9z/3kyZMiq68VcB1i5pIZA/1WYVpcsWKFzHi9uErRPmDmshp59OjRaWlp6k0MNENfNWGJXzRSd+7bt29HNmCihKBToSsj27Ft2zb0DfisFasbMGAA9ozxHt8soC8mfXOk13+moo5g6wUV/Z4UUCKXmlPlVg009MdI/3o152TIoDJ16lSUB70VcI5BhVpJqwQV428HHThwICkpCTfCakrS1wrYkqioKNwv19TUYEW4m5YVpg0zZjHZYTRcpRgNt7G4nlEbMHNZjSxmxrlz5+JesrCwUPRVE1ZtbS0m2fz8/GfPnlVVVSEsoVZNHPX19ZglISsrCxOfWH7y5ImodTKyHubxadOmHTx48OLFi0VFRXgWGT58uKzzw97GgFhFXV2dLPIJ2RfPBOiFn+Xl5TjKeXl56s9K9EfQSVBxsicFlMil5lS5VQMN/THSv17NORkyqOAUxYPm2LFjjx49igH37duHQ7Z//35R6xCDCrWSVgkqUFZWlpGRgYstPj4eYxr/jgH0tfDq1avc3FzxO6MQ0EAUCrLIB9c8RouJiYmNjR0/fryYgwJmLs3IuGh79eqFrRo1atTu3btRi8te1vn+aCMxMbFjx445OTnFxcWoVRPHvHnzfIM1s3TpUlELYY+sV11dPWnSJPT1eDx4dJgxY4Z5T0JKSgomJpnxC9kXDwQ4HxAtsNlxcXFofO/ePVmnPYJOgorDPQkokUvNqXKrBnqaY6R/vZpzMmRQAcQVPK+89957iOII/LNnz7Z6T7KlGFSoleiCiiCy/zXYFZ06dWpsbJR597TeyEFhzsJBPHLkiMz/iwTsSXG6gsgSUUS8432HiYnp35KIIs10UjIxMbXdRBRpppOSiYmp7SaiSDOdlExMTG03EUVa0+kY8EE9EbUNKqIgEUVa0+loDiryl2nC+nUa2dNHFtkmu1l0lHWtuVVBa0UvkPm/i1yrjyxyiRzUYlhZ5yOLbJPdfGRRMPpasktFFCSiSGs6Hc2/UiwWoKUXf0D7FnXXr1dfqxfQ3qo7ys1VxhJzrRMjR470rfCtgL8ABRTKJZ+ArBPGoczDOlmvzb4ot6qillERBYko0ppOR2NQMV/t9q9/JzOFfr1/z1ahZUBjc19zSdh+/fXXu3fvrlmzBmMGBBUX1xJA/4qcrNd+X7R0siJqoiIKElGkNZ2ODCogmgU0drJem7Zu3Yox/ztBRTRzsiJqoiIKElGkNZ2O+g/q7V//Ls4UIYdyfatEMxfXK4T8UljXg8qbN2/Gjh2bnp7+6tUrUVJZWenxeHbu3Ill88jGEifrtdlXNHOyImqiIgoSUaQ1nY7uBhX8FERhi8ieNvraaSOIlr5R3xKFAVS5VQMlZAMjO18Kqwkq+CmIQvt+//33bt26ffHFF1jG6nr27Gn8ki7jgAGDi+zbVfqIQptEe9ERRGEAVW7VgFpGRRQkokhrOh01QaVFF//bucTQvkV9A+j7mmvfrrg5WWFvq1Rh0FpFX2tm50thrYKKcV3GZZtKS0ujo6PPnDkzc+bM1NTUZ8+eyQofMT7IvF9AobmBhp2+qjBoLbWYiihIRJHWdDpaBZWWXvnm9k7mDqu+rm+VMasZvKXrBTtfCmvz7a8w1v7ll1/GxMR4PJ7KykpZ5GMcKmBYJ+sN2deYtT8s6aiIgkQUaU2nY9CgEsZlH3JaaZGgfV3fqoBaq/HDWK/NL4VtvaBSXV2NXunp6cbvetKP7GS9LRrZ/rCkoyIKElGkNZ2O5qAS3jWvn1b07PRtja3Cspms8zOX2BRn40thv/76a4zvelBpbGz84IMPMjMzY2Njv/rqK1kaamQn6w05spmso7CpiIJEFGlNp2NAUAm42u1f/OaWLvbVZzVCjmwUsrH99YKdL4UVX1hr/i4yueTXovXCkiVLunTpggD2/fff42nphx9+EOX6kZ2st0V97Q9LOiqiIBFFWtPpaAwqIacGZM1tFGOVuZmvazh9zb0045hpRg7Q0vUia26j2PlS2J9//jk6Ojo3N/fSpUs//fST8YunxAKYV+FbreV6T506hVqsVGQ/+eSTpKSk+vp6kTV2NA+ir9Wz37elI1NwKqIgEUVa0+kYEFTMRAfBXBJANACZN7AqV0QDkHk/WdqcrLNH9mnh2mW+OVnnYy4JYOdLYb/77jtM+mIo45tjogRk3sCqHDACVjdnzhyZ93qfPn2anJw8YcKEN2/eiBLRHUQ2gKzTvi4rsqe2r2wR1vjUjIooSESR1nQ6Bv2gnoj+6VREQSKKtKbTkUGFbJKPGM3JOvr7qYiCRBRpTacjgwpRm6QiChJRpBlORyYmpraeiCLNdFIyMTG13UQUaaaTkomJqe0mokgznZRMTExtNxFF2tvP51UyflAf2d/nKSkp2bFjh8w0Z9ywMDZSM7Jz2dnZEyZMkBnSOnLkSJ8+fTweDw5icXGxLPULe0/qTwlVa//MQUsN2YiI/P6hQWX27NlDhgyRGQPzVrV0O61GdkVeXt6iRYtkhqw1NjbGxsZOnTq1srLy6tWr6nsBlLD3pP58ULX2Txu01JCNiMivrQYV3xX9dlmV2NSqQYVsqqurw4E7duyYzLvH6nx4e7qYyDot2dSCbEREfuEHldu3b6MN5oXly5d369atXbt26enpL1++FLXl5eUZGRkdO3bs0qXL9OnTA/5Voqa2Q4cOvqu1GdUAy+qnWFDLUFtbm5ubm5qaio1JSEiYNWvWo0ePZF2okaGoqKh3795oNnLkSPEfHm/duiWqNmzYgHKxDLi5Ru2FCxdk3usVb+NA0DdtnIyshz2ZmZmJPYlBBg8ejBWJ8hcvXsTFxa1cuVJkBTwZ9O/fX2as+yo7d+5EAEZtfHz8lClT7t+/Lyu0R1C8orNnz+J8aN++/cCBAy9fvizrfLW+/dSM8e2vsPekgBK5ZKKqNG3M0FhDNiIiP6dBZdCgQQsXLrxx4wau7XXr1jU0NKDq2rVrmNbHjRtXWlq6f//+Hj16YH5R/9ddX/vgwQNMXpj+MBlhQXj9+rWoFVultg0LahkqKirmz59/6NAhTMpHjx7FrJeWlmZzZEyCUVFRiEmYLgsKCjBXYmT7U78YPCsryzwVOhxZA1M5pvWJEyeWlZWhy9atW9evXy/rvF4cl8TERPUC6+vrMcVv2rRJZPV9IT8/Pzo6etmyZWhw4sSJmTNnqv9NqT+CeEXomJOTc+fOHTyRYPZPSUlR/6T5+fPn2FGXLl3Cy0TQ8h2E+wiBohbC3pMCSuSSiarStDFDYw3ZiIj8nAaVgC+bEvCI0LVrV/XUcubMGbQ8deqUyOprBf3bX2rbsKCWzcQEXVVVJfM+ViNnZ2djZlT/bBHBCX1bOvUH/XjZlZGDOnfuHBqji8w3h0iPWrVjCwsLEQkeP34ssvq+mNkRGNasWSPzPips6I8gXhGy6gWePn0a2ZqaGpEVEDZQWFJSIvMmYexJASVyyURVadqYobGGbEREfk6Dyp49e2TeoF+/fpMnT5YZ36eyxhlKXyuEF1RwV75582Z0jIuLw0yNG3PU2gxXMTExuCuXGa/38OHD6OtKUHFl5KAePnyIOIFHgePHjxvf6FOGDRumdvXQoUPxlCaWQd9379692Azzv1IW9EcQr8jj8aip/8qVKxjq4sWLIiuEF1T0e1JAiVwyUVWaNmZorCEbEZGf06ASMGUL3bt3N/7TdYiNjV28eLFY1tcKdoKK8aewevVqTJQFBQXXr1/H5pWWlqI2YOYKOjLuwdFy7dq1Mu97pwUlzoOKWyNbqaioGDNmDGZb9Orbt+/58+dlhc+uXbvE08nNmzfRAKuWFT6avlu2bEHh8+fPZb45/RG084rCCCoh96SAErlkoqo0bczQWEM2IiI/p0Hl9OnTMm/Q2k8qagE/VQkkJycvWLBAZrxe8ca9naAC+rvgjRs3GifKyspK1LrypGJzZD08oqFLenp6QkKC8SsmGxoaOnfujKc3bEBqamrQb58M2tfhk0prBBUI+0kFhWayLhTZ2oJsRER+rRJUgr7nfvLkSZHV1wqIDZi5ZMZAv1WYFlesWCEzXu+qVavQPmDmshp59OjRaWlp6k0bNENfNWGJXzRSd+7bt29H1mZQcWVkO7Zt24a+4nclFKxuwIAB2DPGe3yzgL6Y9M2RXv+ZijqCrRdU9HtSQIlcak6VWzWwgvYashER+bVKUDH+dtCBAweSkpJwI6ymJH2tgC2JiorC/XJNTQ1WpH6FCQI2zJjFZIfRMMtgNNzGYiZFbcDMZTWymBnnzp2L55vCwkLRV01YtbW1mGTz8/OfPXtWVVWFsIRaNVHW19djloSsrCxMfGL5yZMnotbJyHqYx6dNm3bw4MGLFy8WFRXhWWT48OGyzg97GwNiFXV1dbLIJ2RfPBOgF36Wl5fjKOfl5ak/K9EfQSdBxcmeFFAil5pT5VYNrKC9hmxERH6tElSgrKwsIyMDk0t8fDzGNP4dA+hr4dWrV7m5ueJ3RiGggSgUZJEP5mWMFhMTExsbO378eDEHBcxcmpH37dvXq1cvbNWoUaN2796N2rt378o63x9tJCYmduzYMScnp7i4GLVqopw3b55vsGaWLl0qaiHskfWqq6snTZqEvh6PB48OM2bMMO9JSElJGTt2rMz4heyLBwKcD4gW2Oy4uDg0vnfvnqzTHkEnQcXhngSUyKXmVLlVAytoryEbEZGfLqgIIvtfg13RqVOnxsZGmXdP640cFG7kcRCPHDki8/8iAXtSnK4gsm6Rg1qQjYjIzzKo/Ne8ePFixYoV586dwyx89OhR3MIvWbJE1jnTeiPrPXz4EM8Hw4cPx5NKwLuLbVRE9qSMHhZkIyLyY1CR/vjjj3HjxnXt2tXj8bz//vurV692ayJuvZH1li5dGh0dnZ6eftXiLxzbnIjsSRk9LMhGROTHoEKkI6OHBdmIiPwYVIiIyDUMKkRE5BoGFSIicg2DChERuYZBhYiIXMOgQkRErmFQISIi1zCoEBGRaxhUiIjINQwqRETkGgYVIiJyDYMKERG5hkGFiIhcw6BCRESuYVAhIiLXMKgQEZFrGFSIiMg1DCpEROQaBhUiInINgwoREbmGQYWIiFzDoEJERK5hUCEiItcwqBARkWsYVIiIyDUMKkRE5BoGFSIicg2DChERuYZBhYiIXMOgQkRErmFQISIi1zCoEBGRaxhUiIjINQwqRETkGgYVIiJyDYMKERG5hkGFiIhcw6BCRESuYVAhIiLXMKgQEZFrGFSIiMg1DCpEROQaBhUiInINgwoREbmGQYWIiFzDoEJERK5hUCEiItcwqBARkWsYVIiIyDUMKkRE5BoGFSIiconX+/955IQrhJIUwgAAAABJRU5ErkJggg==" alt="graphic" title="graphic" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查看已安装的CentOS版本信息（CentOS6.8有，CentOS7无该命令）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABOwAAACUCAIAAACvGSFtAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAADZ+SURBVHhe7d37UxRX3gbw9+9YBAbkqsiLFaOA5ZaiEF+ToCZeMN5KTZStEsTLKkopvkY3iiaasqw1/rC+64vlRo2aFeMlJd4p73iBF/AGZBcRNWgUEBXH98l820473aeZQdSZyfOpLuuc02e6z5zTPcwjA/zH//zP//zpT3+6+e9/cePGjRs3bty4cePGjRs3bj6+aSG27t//4saNGzdu3Lhx48aNGzdu3Hx800PsT9y4cePGjRs3bty4cePGjZuPby9C7L9+4saNGzdu3Lhx48aNGzdu3Hx800Ls8aOHuHHjxo0bN27cuHHjxo0bNx/ftBD7nIiIiIiIiMjnMcQSERERERGR32CIJSIiIiIiIr/xW4j9wwuy4w3Tzv02zv7Xv/71448/1iq/A1evXk1JSenSpUtERITW5OLhPGjrZLVSeqPlXjMMwHWkP1RUVGhNtkaNGrV27Vqt8hp4Ox4iIiIiInrzXgqx0vRalZWV4URPnz7V6i7GU3s4DMvjdExxcfFrjUa+Zvr06VOmTGloaGhsbNSaXLyaB8tl0hs9XERobW1FZx8JseDVeIiIiIiI6M3zoRCLf/WCq9lOJ4bY35vhw4dv2LBBq3SU5RrpjZ6soGCIJSIiIiIir3gaYqdPn56bm7t06dLY2NiIiAhJQS0tLbNmzYqOjo6KikKHhw8fSmfL9urqapzCSD68ioLlv7B79+60tDSHwxETE5Odnf3gwQM0qo4D9+7dGz9+PPqnpqYuW7Zs0KBB0v7JJ5+sWbNGyrt27erdu7eUi4qK3I4gvD2OChLRzJkzMfjQ0FAcqrS0FI01NTVBQUE//fST9Hn27FnPnj137NiBsmV/ceHCBQwyLCysW7duCxYskEbV/GOc6JORkYHFGjhwIM6ot8vzFfrHib2dB0BnrfSCHMFI22HLMjSq5gEhFs/rww8/xJAw1Js3b0q7Dcvrtq2tbcWKFQkJCTgOjllbWyudwXI8qv6W1yeoxm9zXq+ozktERERE9HvgRYjt0aPHqlWrkJQaGhpOnTqFRiSKvn37Il+Vl5cjLOH9tHRWtYP5O6io6qeWgl7dtGkT0h3e6yNUpKenz5gxQ9rB8juxmZmZQ4YMqaysPHr0KEKLh+HT/LOgHTuOGSJTnz59Ll68WF9fv3fv3nPnzkk7Ypj+HcWSkpLIyEjEUZRV/evq6pBU8/Lybty4galYvHixtKvmGeOMj49H2sf8TJgwISsrS9rF0KFDLb8T6/k8gL5GRm7r6AnL0KiaBwS/8PDwPXv2YB4Q0VGVdhuW1y0CeUpKyvnz5zFFc+bMMT4vy/Go+quuT9X4bc7rFZv7goiIiIgo4HkRYt3eczudTiSrbdu2SXXfvn0Oh+Px48eqdql6FWKNioqKEAy0itVxmpubQ0JCjhw5IlUEPH3AXoXYDh/HbOnSpSNHjtQqBphzZE4pz507Vw+Zqv4FBQXmwGMzzxhnbm6utG/ZsqV///5SFh6GWJt5AMs1cltHT1iGRtU8ILUiV0v58uXLeCByqVRVzNctpqhr164lJSVSffToUXBwcFVVlVTN47HvrzNen5bj9/A43nK7L4iIiIiIAp4XIXb27NlaxeXu3bt4yKVLl6RaU1ODanV1tapdql6FWDwWY0tKSkpISIiNjY2Li5N2MB8HwQMtt27dkiqeV8dCbIePY4ZJiIyMxMPz8/OPHTumtT5/3tjYiHyIAIPx40kVFxdLu6r/5MmT58+fr1VesJlnjHP16tXSjnG6hRwPQ6zNPAB2aSUDvdFyryXLEKuaB4TYr776SsrIhHigfGfVhvm6raysxAPdHD9+XPaax2PTX3V9Wo7f/ryWtE4uWpOLzX1BRERERBTwvAixC178KKZ43SHW6XTibXp2djbCXkNDw/bt27t37+7q9SuvQuzYsWP18Llz584Oh1j741hCXsXIs7KykFoLCwu11ufPJ06cuGLFikOHDsXHxxufhWV/hFi3yQf7EGsTtl9TiEWLG22HLcsQC5bzYA6xp0+flqqK+bqVMKn6Fq4qxJr721+f5vHbn9dz9uclIiIiIgp4HQ+x5o+zhoaGIlqo2qUq0ejJkydSBVTl1MZ/AfEJ5bq6OqmuX7/e+GbdfBy3j7/m5eXpoWvKlCnLly+X8jfffGMfYjt8HHsLFy5EcNUqz5//85//TE5OzsnJwfG1ppcZ+xcUFKSmpkpZZzPPnRJibeYB9GXS6S3mXTba2tqCgoKMv8LKjXEeEGKnTZsmZfk48e3bt6WqYr5uMUXh4eFFRUVa/WXm8aj621+fOn389uf1nIfnJSIiIiIKVB0PsZCbm2v8xUL6z3aq2uH+/fvBwcFIVi0tLRJB5bzGfwX2IqRt3boV5draWiQx45t183EgMzPz/fffv3btWklJSY8ePfTQtXr16rS0tNbW1nv37qWkpNiHWOjYccwKCwv37Nlz8+bNsrIy9F+yZIm2w/UdPzw7pMQzZ85oTer+8oudFi1aVF1dXVVVpQdp1Tx3SogF1TyA+WrRW8y77OGw8+bNq6+vx6xKi2oeEGLlFzthHjIyMsaMGSPtNiyvWxywZ8+eBw8exMQipU+dOlXb4WIej2V/m+tTNX7783rI/r4gIiIiIgp4rxRim5ubc3Jy8JY6MjISB9H/1IeqXaxbty4uLg6n0yOT8dTG8v79+xMTE3v16oXchUe5vVk3H6exsXHcuHEOhwNR84svvhg8eLC0I/GiT0xMTHp6ekFBgR7q+vXrh4cbycdxvT2OCpLGgAEDkFQxFZiQpqYmbYcLWtyOYNMfSRWnDgsLw9kRsaRRNc/ehlhv5wHQRyu9oLeYd9k7e/ZsUlISHoWBSYtqHhBicRF+8MEHoaGhI0aM0D/qbMPyum1ra1u5ciWuK5wCk+P2zXDzeFT9Vdenavz25/Wc/X1BRERERBTYXgqxQna8Ydq5O+/sSHETJkzQKq+gs45jhqj5l7/8Rav4PH0etHV6S9cJERERERH9zv0WYgNAaWnpoUOHmpqarly5kpiYKB+57IDOOo7KL7/8cuDAgeDg4GvXrmlNPul1z0On0CK1gbaDiIiIiIgCUUCF2BMnTiQnJ4eEhMTHx69atcrpdGo7vNRZx1EZPHhwVFSU5Q+m+pTXPQ9ERERERETeCqgQS0RERERERIGNIZaIiIiIiIj8BkMsERERERER+Y0ADLHmv3fq70aNGrV27VqtQi6bN282/t3aTsF57lzGP/VEHfM6rnPqXL52nduPJ/C+PnrCH+8jrmPnejP3aWetC+/Tt4vr+LZ4e5++FGLnzp2L9/FSNrp9+3ZmZmZ0dLTD4Xjvvfe+//57ae/evbv8PtiuXbuOGDHi//7v/6Td0tdff52UlKRVXHCoJUuWaJXOU1xcHGBR5K2HK89/5a9cD1rldXJ7sZPzglZ/mardDee5c/nam/t2+eD8v47r/K3zfJyu5+rrT8rXrnP78byBr48+uL7+eB9xHTvXm7lPvVqXsrIyzNvTp0+1ugHvU5DzglZ/maq9U3Ad3xZv71OPQuywYcOGDx9eUlJSWVn57bff6r9WFyH2yy+/vHr16oULF6ZMmdK7d2+bX2B76dIlTN+//vUvqd67d69Lly4nTpyQKtl4u+HK8+te7+Zh/1dhfLEzns58arSYGy1xnjuXr725t+eafp+b/9dxnb9dno9T7+Zh/7fF167ztzse1/L63Pr6433EdexcPvj1yCb8vG6/rq7vra+ffr3jOnYib+/T9kPsgwcP8JzPnz+v1Q0QYjdt2iTlw4cPo9svv/wiVTPk2//8z//8xz/+IdU9e/ZERUXJqre1ta1YsSIhIcHhcGAAtbW10gemT5+em5u7dOnS2NjYiIgIyc+tra0zZ86MiYkJDQ1NTU0tLS2VzkVFRRgDuH37vqWlZdasWdHR0TgjDvjw4UNpx2QtWLAgIyMDBx84cGBNTY204whSeMNU58WcYJwffvgh5gdP7ebNm9K+e/futLQ0NGIqsrOzsVLSrpofm3lWkSF5MiFufTx5iOX4i4uLk5OT8UKGcW7cuLFbt25DhgzBM8Kun3/+edy4ceiPR33++efyYmd/Isvxqx4SqPOsus5V94Xq+YLl/QgXLlzAjIWFhWG9cC5pxHmXLFkyceJEtHt4f1keRzVOsBwPr3Nh85CAnGd/uc7v3bs3fvx4nBcvGsuWLdPftIHleVXjxEhmzJgxePDguLi40aNHNzQ0SLtqPKqvj2D5vFSvbzbkKcu/9tz6ePIQX7uPuI7g1seTh/jLfapaF7A8r2pdLJ9vdXW1dNbpj/LqOGBzHarg4Pq/9tz6ePIQX7tPuY7g1seTh6jG+bbuU5X2Q+zjx4/Dw8O//vprrW6gh1hci3hs3759pV0FVxKejJTnz58/efJkKWPBUlJSkJNxQcyZM8fti0GPHj1WrVqFmcLFd+rUKTRiCvr06XPx4sX6+vq9e/eeO3dOOgvzZ9AxOxgb5qu8vByTgvmVdkxWfHw8ToosPWHChKysLGn3ZIFfB9V5sShYAsT+Gzdu4JLS1wiTv2PHDrx3rKioSE9Px/RKu2p+bOZZRYbkyYS49fHkIZbjx4sdnixa8HLTr1+/69evv/vuu4cPH8auzz77DC98VVVVBw8exP3T6S92gTrPqutcdV+oni9Y3o91dXV4RcvLy8O84SGLFy+WzjgvXiJ++OGHK1euYOHavb9Ux1GNEyzHw+tcqB4SqPPsL9d5ZmYmulVWVh49ehRf1I3zZnle1TjxZQ7jwZXT1taGJ4U3gtKuGo8wf31UPS/7r7OWXuv6+tp9xHUED+fKyF/uU9W6gOV5hXldVM8XbL6D5/lxbK5DFXnKniyWWx9PHuJr9ynXETycKyPVON/Wfari0ceJN2/ejEycmJiYk5Pz448/aq2uEBsSEoLk3aVLl3feeQfj0HYofP/99z179pTyH//4x//93/9FASG5a9euJSUl0v7o0aPg4GBc0FLFkzevJQL9yJEjtYqJ20XjdDoxU9u2bZPqvn37MGCcFGVMVm5urrRv2bKlf//+UvY1WBRc31K+fPkyrj9cB1LVFRUV4QuVlC3nx36eLekX+mu6SYz08ePFbsCAASisWLECq4/C+PHjsTq4+jFgedUDXKvGFzv8K1w7NXrVrV0lUOfZ8jq3uS+MjM8XLO/HgoICyxdcnBevGFLGzd7u/WV5HPtxmsfD67xdgTrPfnGdNzc344vmkSNHpIo3BMZjWp7XyDhOfJmbM2eOlPHWJygo6Oeff0bZfjzmN1Wq52X/ddbsda+v0Vu/j7iOQjWHNvziPgXVuoDN+lqGFtX7TG/Dj/k49tehpde9vkZv/T4FriO49fHkIZbj9MH71KMQC42Njd999x06REVF4V9pRIiVn4ktLS2dPXt2cnIyVkJ2Wbp//z6uV/RHNsAkIoijEVcVym6OHz8uD8GTx5GlrLt06VJkZCSef35+/rFjx7TWF9wumrt37+KAeIhUa2pqUK2urkYZk7V69Wpp37Vrl3HSfQoW5auvvpIyLheMX/4nA88Fa5eUlJSQkBAbGxsXFyd9LOfHZp61iot0FnrVrd2S6rGAsk5rcrEcP17sBg8ejMKaNWtk6T/77LONGzdWVFTg4fX19b8+8vnzv/3tb3IDuB3WsmxstBGo82x5ndvcF6rnC5b34+TJk+fPn69VDHBefT49ub8sj2MzTjCPh9d5uwJ1nv3iOpf1vXXrllTx9VfWV1ieVzVOfJnTf4b/0aNHOOx510/92I/H/KZK9bwsX98AJ9JpTS561a3dkuqxgLJOa3LxqfuI6yhUjwWUdVqTi1/cp6BaF7A8r7AMLar3md6GH/NxbK5DtOukRehVt3ZLqscCyjqtycWn7lPgOoLqsYCyTmtysRynD96nnoZY3dGjR4OCgu7cuYMyQuymFz8Ti/WLiIjYuXOnVFWGDh2KC3f79u0pKSnSIm+GzN/yEnjyC158VNoIoRoHycrKCgkJKSws1Fpd3C4am0nHZOk/QIzJ6t27t5R9jTlcnT592ul04nLJzs6uqqrC7GE2sBzSB8zzYz/PZuislV75JrGkGr/lix1e2uQm18f/97//XX+xkxahV43t7Q5GBOQ8g+V1rrov7J+v5f2IFyPLm9Tb+8vyODb3L5jHw+u8XQE5z+AX17nNmxUwn9dmnPgyp/+YT1NTEw4rnxS1H4/5TZXqeYHN11k3xgUyllXc+rT7EF+7j7iOwq2PJw/xi/sUVOsClucVlqFFdV5vw4/5OPbXoRk6a6XXs76+dp8C1xHc+njyEMtx+uB96nWIlUHjX5QxSj3E4jlER0frVRVcTJ9++unMmTM///xzaUFaCA8PLyoqkqobm4tMLFy4cOLEiVrFxe2ikYEZv/0dGhqKk6KsmixPFvh1UJ0XizJt2jQpy8dcb9++jcsdBfluNqxfv9540ej0+bGfZzMc3I22Q8GtQ7v9VeNXvdjh1QdfgPVXnyVLlrT7YudG2sFYNgrIeQbL61x1X9g/X8v7saCgIDU1VasYeHt/WR7H5v4F83h4nUs7GMtGATnP4BfXudvHxvLy8oxvVszntRknvszpP/6ACyYoKAjvMFBWjUeY31SpnpeR+eusGwzSjbZDwa1Du/197T7iOgq3Du32B7+4T0G1LmB5XuFV+JHo8uTJE61u4OFx7K9DM5zOjbZDwa1Du/197T4FriO4dWi3P1iO8y3epyruIfb9998vM5DFxhpg0JWVlSdPnhw9enRiYuKzZ8/QjlHKx4nxjn/x4sXBwcFXrlxxHUkJCb5bt27vvvuu8Y/r4MLt2bPnwYMHMQVYxalTp2o7FE++sLBwz549N2/exAhTUlLwcG2Hi/miyc3NNf4gctaLHxRWTZYnC/w6qM6LcIX3i3jK1dXVGRkZY8aMQSNuGFxMW7duRbm2thaD1y8a1fzYzLM9D28Avd2TCVSNX/VihwIGjBejtra2+vr6hIQE/SY3ns7y1G6NquEF5DyD6jq3vC9sni9Y3o94mnjIokWLMG9VVVXLly+Xdm/vL9VxVPcvWI6H17lQDS8g5xn85TrH4uLr7LVr10pKSnr06GF8s2I+r8048WVOXq+uX78+YsQIDEPaVeMR5q+Pqudl/3XWxutYXx+8j7iOQm9XdXCjel6+dp+q1gUszyu8Cj/yE3ZobGlpwTPVWl08P47NdWjvdayvD96nXEeht6s6uFGN823dpyruIRZPzwgLgHa86vXr1y8kJCQyMnLs2LFIrdIfo5RuDofjv/7rv/bv3y/tNpDjcZlGvfjjOgKX78qVK3v16oVTYMR5eXnaDsWTx0wNGDAAnTEFOTk5TU1N0o5Bynh0mB20Nzc3oxs6Y/x4svqvfvZ2st4WhCtMwgcffBAaGoqb8NaLjxxgwhMTEzFvQ4cOXbdunX7RqObHZp5taFNpuO6NZTduPe1Zjt/mxa6xsXHcuHG4vfGVGLuMN7mcF7S6gbbDg1EF6jyrrnPVfaF6vqB60ccrGl6pw8LCYmJi5s2bJ40duL8sj6MaJ1iOh9d5uwJynv3lOpf1xRfNtLS0L774Qi4DYXle1TgxEsz5kCFDcKiRI0fqr1eq8ai+PoLl81K9vtnTDv0a1tfX7iOuo86tpz1/uU9V6wKW51Wti/158Yzi4uLQGaeTFm+PY3Md2tAO/RrW19fuU66jzq2nPdU4fe0+fSnEEhER0ZuBr9YTJkzQKl7Cl3/5X2Z667iOgccf1+VVrsNAxXUMbAyxREREb0hpaemhQ4eampquXLmSmJi41fURrA5g+Hm7uI6BzV/WpbOuw0DFdQxsDLFERERvyIkTJ5KTk0NCQuLj41etWuV0OrUdXmL4ebu4joHNX9als67DQMV1DGwMsUREREREROQ3GGKJiIiIiIjIbzDEEhERERERkd/wIsT+1fT3jjqX+fijRo1au3atViEiIiIiIqLfvd9C7M2bN+WPCEVGRqanp+/du1d66IqLiz2PlGVlZTiU8Y/Btst8/A6E2A6c13OTJk1yzZAGw0Nju/PmlfPnz3/00Ufh4eHR0dFTp07t8M92W85Da2vrn//8Zxw5KioqOztb/3txt2/fzszMRLvD4Xjvvfe+//57aSciIiIiIvI17iEWAaa0tPTLL78MCQnZuXOndOqATgmTvhZi//3vf1dUVEyYMGHkyJEo/PTTT2jsxHnDMRFfFy1aVF5efvr06dmzZ7e1tWn7vGQ5D/n5+e+8887x48dPnjyZlJQkf3Iahg0bNnz48JKSksrKym+//XbDhg3STkRERERE5GvcQ+y5c+dkx+LFi5OTk6VcVFSEXeD2cd/W1taZM2fGxMSEhoampqYixaGxurpaOuuMj5o+fXpubu7SpUtjY2MjIiIkL6mOjxC7YMGCDz/80OFwYBdGKO2ffPLJmjVrpLxr167evXujYHPelpaWWbNmyXcgMYCHDx9KO1iOxx4eMmnSJK1iO29ol4KHPv300zFjxmgVA0TZFStWJCQkYB4wJ7W1tdKOecD8ZGRkYPADBw6sqalBo2oecBCs1MaNG10Pfb5169awsLBHjx49ePAAfc6fPy/tREREREREvkwZYo8cOYJqfX29VMH8M6uIfH369Ll48SK67d27V38sqL4jigTYo0ePVatWIUk2NDScOnVK26H4mdjw8PA9e/bcuHEDUU0+vguWIVZYnhdJr2/fvhcuXCgvL0fYy87O1nbYjkfFPsQa5w0FafSE0+lExsZyaHWDZcuWpaSkIGcioM6ZM2fQoEHSjnmIj49HI57vhAkTsrKypB3M84CIi5azZ89KtaqqCtVLly49fvwYk/z1119LOxERERERkS9ThtiKigpUEfykCuaQuXTp0pEjR2qVl9mEWD2DubEMsZmZmVK+fPkyDoicibJXIRbhMDo6etu2bVLdt2+fw+FAcpOqzXhU7EOsed48dP/+fTzwwIEDWv0FDLVr164lJSVSffToUXBwMCIoypiH3Nxcad+yZUv//v2lDOZ5QAZGy7Vr16SKmUT18OHDKG/evDksLCwxMTEnJ+fHH3+UDkRERERERD7olULspUuXIiMjEQLz8/OPHTumtbrYhFj9RzHdWIbYr776SsrIcjigfKfUqxB79+5dtGCoUpVvSFZXV0vVZjwqbzjEVlZWot3N8ePHsQvzsHr1aumGeejTp4+UwasQC42Njd99993cuXOjoqLwrzQSERERERH5GmWIRbxB1f7jxIDws3379qysrJCQkMLCQq3VNsQuWLBAq7zMkxB7+vRplMeOHauH2J07d75iiFWNR8U+xJrnzUNOpzMyMtL8cWIJsfItaDdehXnVx4mlqjt69GhQUNCdO3e0OhERERERkS9Rhtj8/Hz9FxQJyxCrW7hw4cSJE7XKi29IPnnyRKu/4G2InTZtmpTl48S3b99GecqUKcuXL5f2b775xhjezOc1f5w4NDTU+HHizg2xxnlDuxQ8hMNmZGRolRcw1PDw8KKiIq1uYBNizfPQ1taGeTD/Yiep6iTr4l+tTkRERERE5EvcQ6z8qZhVq1aZ/1SMOWQWFhbu2bMHDywrK0tJSVmyZIm2w/Xh2ODgYCSrlpYWY5TyNsTKL3aqrq5GutN/c+/q1avT0tJaW1vv3buH8xrDm+V5c3Nzjb/YyfgLkLwKsbW1tXim48aNwzhRkKRnM2/ehlgEdYfDgRiMcZ45c0b/EzuY2J49ex48eLCuru7IkSNTp06V/jYh1nIecORevXqdOHHi1KlTxj+xg6eDkF9ZWXny5MnRo0cnJiY+e/ZMdhEREREREfkU9xALERER6enpP/zwg/SAfv36yS6dfBx369atAwYMQGyLjo7OyclpamqS/mLdunVxcXHobIymlqFRdXyEWHT+4IMPQkNDR4wYcevWLemPhIZjxsTEYJwFBQXG8Abm8zY3N2N4GGRkZCSe7IMHD6QdvAqxkyZNkuEJDA+NNvPWAYiXw4YNCwsLw2inTZvmdDrRiCi7cuVK5E9MNZ5sXl6edLYJsWCeB8T+OXPmyDxkZ2fr64WQjCXAwdE+duzYq1evSjsREREREZGv+S3EEhEREREREfk4hlgiIiIiIiLyGwyxRERERERE5DcYYomIiIiIiMhvMMQSERERERGR32CIJSIiIiIiIr/hQyHW/HdiO6azjkOdJSIiQv4KUUVFhdb0+4CnrJU88OrXrWqevRoGEREREZGP+y3E6n/vNDIyMj09fe/evdLjjSkuLl67dq1WaU9ZWRmG+vTpU61u4NVxfM358+c/+uij8PDw6OjoqVOnyt+J7QDL+Wltbf3zn/+MI0dFRRn/Tuzt27czMzPR7nA43nvvve+//17aOxFOjfH4Toj99UJ30eq2tK4uWpMHzJ1xWcpBdEePHtX2ddJ1q5pnNGolIiIiIiI/5x5iEWBKS0u//PLLkJCQnTt3SicfZBNi/ReyB+LrokWLysvLT58+PXv27La2Nm2flyznJz8//5133jl+/PjJkyeTkpJwfGkfNmzY8OHDS0pKKisrv/322w0bNkh7J/KpEGtMdMayJbcO7fYXlt2QURMTEzEJuubmZm1fJ7GZZw9HTkRERETk49xD7Llz52TH4sWLk5OTpYwotWLFioSEBIfDMWrUqNraWmnHO+aZM2fGxMSEhoampqYi/Uo7XLhw4eOPPw4LC+vWrduCBQu01ufPp0+fnpubu3Tp0tjY2IiICMlLRUVFODW4fZwS1RkzZgwePDguLm706NENDQ1orK6uls46/VGq47S0tMyaNUu+A4kBPHz4UNo/+eQTjC0jIwODGThwYE1NjbTjCFJ4wz799NMxY8ZoFQPV/FuOXzU/OAhWauPGja6HPt+6dStW59GjRw8ePECf8+fPS7uHLNdRNU6wDFeq/rt3705LS0MjBpydnY0RSrvqerM5rxmGoZU849bfw4dbdkOI7devn1YxUF23qutTNT/AEEtEREREAU8ZYo8cOYJqfX09ysuWLUtJSUHOQUCaM2fOoEGDpA+iS58+fS5evIhue/fu1R9bV1eHxJiXl3fjxg28n0YelnZA+OnRo8eqVauQJBFKT506pe2w+plAVHGc69evI6Xgzfq4ceO0HbbfiTUfB0mgb9++yNXl5eUIAziUtCMkxMfH40nhOBMmTMjKypL2t/J23+l0ImNjObS6gWr+VeMH8/wgAqHl7NmzUq2qqkL10qVLjx8/Dg8P//rrr6XdQ5brqBonWIYrVf9Nmzbt2LEDWRT909PTZ8yYIe2q683mvGYdWFz9IR4+VtVNFWKF+bpVra9qfsAmxEIHnjsRERERka9Rhli8D0YVwQ8hp2vXriUlJdL+6NGj4OBgRCCUly5dOnLkSGk3KigoUAUJhB/VLssQi0wi5crKyqCgoJ9//lmqnodYhEMk4W3btkl13759DocDTwplhITc3Fxp37JlS//+/aX8Vty/fx/P6MCBA1r9BZv5txm/eX6Q8dBy7do1qSJ5onr48GGUN2/eHBYWlpiYmJOT8+OPP0oHe+Z1tBknmMOVfX9dUVERgquULa83D4+jwzDkXyGN7fK2s1Z6mdvPxCYlJWk7XCxDbLvXp3F+gCGWiIiIiAJe+yEW6REFN8ePH0efS5cuRUZGIszk5+cfO3ZMHgiTJ0+eP3++VnkZwo/+o5huLEOs/qtuEE5wXiQxqXoeYu/evYueGKpU5RuS1dXVKCMkrF69Wtp37dplDANvnirE2sy/zfi9CrHQ2Nj43XffzZ07NyoqCv9Kow3zOtqME8zhyqY/1gjXJDJeQkJCbGxsXFycPMTyerM/r5l00Cqe5Tq9jyedQdUNFzPWCEsjrly5ou1wsQyxluurmh9giCUiIiKigKcMsYg3qNbX10tIkJ9HNUP42b59e1ZWVkhISGFhoTQixBp/DtYI4Ue1yzLE6h9zbWpqMg4PGQDVVw+xa9askXaEhN69e0v5rXA6nUho5o8T28y/zfjN86P6OLFUdUePHg0KCrpz545WVzCvo/11ogqx5v6YB8Sz7OxsjBB7cXV1795d22d1vdmf1wydtZKLW9XM2/6g6tOBjxOb19d+fhhiiYiIiCjgKUNsfn6+/GKnx66fmSwqKpJ2lYULF06cOFHKBQUFqampUnbjbYjNzMyUMgaGcIVEKlX5RvGTJ0+kauR2HPPHiUNDQ/GkUFaFwLf1Xn/SpEkZGRla5QWb+bcJseb5aWtrwzyYf7GTVHWSdfGvVlcwr6P9dYKzY/mMv/pL1f/WrVsYQF1dnVTXr19vDGk6/Xrz8PrUuS1uu2vtbX9h2a1TQqz9/JjnWefhyImIiIiIfJx7iJU/sbNq1Srjn9hZsmRJz549Dx48iLfOR44cmTp1qrQXFhbu2bMHDywrK0tJSUE3aZdf7LRo0aLq6uqqqqrly5dLO3gbYpFPcIrr16+PGDEC7+m1Ha4P3wYHB+OdfUtLi1uUNR8nNzfX+Iud9F+Q42sh9vLlyw6HIz8/H+M8c+aM/id2VPNvE2It5wdH7tWr14kTJ06dOpVk+BM7mC6E/MrKypMnT44ePToxMfHZs2eyS8VyHVXjFIMGDZo3b159ff29e/ekxbI/RouLBxkb5draWjwpPaSprjf785oZ11dV1rk1enhtWHbrlBBrMz/CPM/Cw5ETEREREfk49xALERER6enpP/zwg/QARKmVK1ci/yDZ4k1zXl6etOOd9IABA9CId9U5OTlNTU3SDvqf2ImJicFbaq1VEX7wzl5OrZOP++IIONeQIUMQ7UaOHHnr1i3pL9atWxcXF4fO+lt/1XGam5sxPAwyMjIST1b/kyQ2IfBtQbwcNmwY5g2jnTZtmtPpRKNq/u3Hb56f1tbWOXPmyDxkZ2fr64UQiKnDwdE+duzYq1evSrsNy3VUjVOcPXsWyRnjGTp0qLSo+u/fvx9BGu3oiWehhzTV9WZ/Xkuuq+NXWt3FraqTnkJr8oC5syrEqq5b1fqq5keY5xlQ1UpERERERH7uP57/Bzdu3F7eOomPREcmWCIiIiIKJKa379y4cSMiIiIiIl9levvOjRs3IiIiIiLyVaa379y4cSMiIiIiIl9levvOjRs3IiIiIiLyVb/9dmKi3y+GWCIiIiIiP/FKITYiIuIPLhUVFVoTkT9iiCUiIiIi8hO/hdhffvlFEmlQUFC3bt2mTZvW0NAgnWy0trYyxHYiWQLQ6ra0ri5aE3UMQywRERERkZ9wD7H/+Mc/rly5cuDAgeTk5FGjRkknGwyxnciYRdvNpW4d2u1PdhhiiYiIiIj8hHuIPXr0qOxYt25ddHS0lNva2lasWJGQkOBwOJBsa2trpR0sQ6yq/+7du9PS0tAYExOTnZ394MEDacdBZs6cicbQ0NDU1NTS0lJpVx0nIAObt0/KrX9AzsmbwxBLREREROQnrEPsnTt3hg0bNnr0aFef58uWLUtJSTl//nx1dfWcOXMGDRok7WAZYlX9N23atGPHDmRR9E9PT58xY4a0b9iwoU+fPhcvXqyvr9+7d++5c+ekXXWcgAxsHXhS+kMCckLeKIZYIiIiIiI/4R5iu3TpEhwcjMLs2bPlO6WPHz/u2rVrSUmJq//zR48eoUNVVZVUzSHWvr+uqKgIwVXKS5cuHTlypJR1Hh4nYEgQxb9CGtvlVWdSYoglIiIiIvIT7iF28+bNZWVlixcv7tGjx507d9BeWVkpScno+PHjrodbhFib/jU1NThXUlJSQkJCbGxsXFycPOTSpUuRkZGDBg3Kz88/duyYNNqfN/DIE9Qqnn1zVe/jSWeywxBLREREROQnrD9O7HQ609LS/vu//xtlCZOq31SsCrHm/jgm4mt2dnZVVRX2bt++vXv37tq+588bGxvRkpWVFRISUlhYiBb78wYePFmt5OJWNfO2P9lhiCUiIiIi8hPKX+y0bdu26Ojopqamx48fh4eHFxUVSbubtra2oKAg/Vcxgar/rVu3cPy6ujqprl+/3hhidQsXLpw4cSIKNucNyMDmbSj1tj/ZYYglIiIiIvITyhDb2toaGxu7ceNGlJcsWdKzZ8+DBw8igh45cmTq1KnSRwwaNGjevHn19fX37t2TFsv+T548QSreunUryrW1tb1799ZDbGFh4Z49e27evFlWVpaSkoKHS7vqvIEa2IzPS1XWuTUG6py8IQyxRERERER+QhliIT8//49//KPT6Wxra1u5cmWvXr1CQkIQPvPy8rQeLmfPnk1KSsJjhw4dKi2q/vv3709MTEQ7eq5bt04PsUi2AwYMQGek3JycnKamJmm3P29AwjQKre7iVtVJT6E1UccwxBIRERER+YnfQizR7xdDLBERERGRn3j5vTs3btywERERERGRrzK9fefGjRsREREREfkq09t3bty4ERERERGRrzK9fefGjRsREREREfkq09t3bty4ERERERGRr+q03068efPmQYMGaRV6Df76179+/PHHWqVDIiIi5O/xVFRUaE1ERERERER+xT3Enj9//qOPPgoPD4+Ojp46darT6dR2tIch9tWtXbtWQqbO+Gd7i4uL0UGrdFRraysOyxBLRERERER+6qUQi2yD+Lpo0aLy8vLTp0/Pnj27ra1NdrWLIfbVIaMmJiZiFXTNzc3avk7CEEtERERERH7tpRD76aefjhkzRspGLS0ts2bNio6OjoqKmj59+sOHD6X9559/HjdunMPhSEtL+/zzz/UQi+i7YsWKhIQE7Bo1alRtba20f/LJJwsWLMjIyIiNjR04cGBNTY207969G0dA55iYmOzs7AcPHqCxuLg4OTkZx8RxNm7c2K1btyFDhiCDYZfq+IhnUvBTCLH9+vXTKgZFRUW/flv2D39w+zixV/MpGGKJiIiIiMiv/RZinU4nMiqqssMISalv374XLlwoLy9HWEIukvbPPvsMwbKqqurgwYN4rB5ily1blpKScv78+erq6jlz5ujtCF3x8fFofPr06YQJE7KysqR906ZNO3bsQBZFuEpPT58xYwYaEWLDw8PRMnr0aES769evv/vuu4cPH8Yu1fERz6Tgp1QhVph/Jtar+RQMsURERERE5Nd+C7H3799HvDlw4IDs0CHcRkdHb9u2Tar79u1zOByPHz9++PBhcHCwpEqYO3euhEns6tq1a0lJibQ/evQI3RB0UUboys3NlfYtW7b0799fykZFRUV9+vRBASF2wIABKKxYsWL69OkojB8/Ho+yOb6/c/uZ2KSkJG2Hi2WI9Xw+BUMsERERERH5tfZD7N27d9F+6dIlqdbU1KBaXV2NIIRCfX29tP/tb3+TEFtZWYl2N8ePH8cuhK7Vq1e7uj/ftWuXHq5wTIwBmS0hISE2NjYuLg6NCLGDBw9GYc2aNbNnz0bhs88+27hxo83x/R1CLOak7IUrV65oO1wsQ6zn8ykYYomIiIiIyK+99HHiyMhI88eJ7UNsQ0ODtP/97383hli93QihC4lUyghdvXv3RgHnRdzKzs6uqqrCo7Zv3969e3e0W4ZYRGWb4/u7Dnyc2PP5FAyxRERERETk1176xU6TJk3KyMiQss78ceLQ0NDHjx83NTWFhIScO3dO2pcsWaJ/nDg8PLyoqEjajSxD161btxCr6urqpH39+vX2Idbm+DiOVvJPnRJiVfMp2tragoKCSktLtToREREREZFfeSnEXr582eFw5Ofnl5eXnzlzRv8TO7m5ucZf7KT/AqGpU6dmZmaiT319fUJCgv4LlhBoe/bsefDgQUSpI0eOoJu0W4auJ0+eICRv3boV5draWjTah1gUVMdniEVBNZ86LNO8efOwZPfu3dOaiIiIiIiI/MRLIRZOnTo1bNiwsLAwBKFp06Y5nU40Njc35+TkoCUyMhKd9T/Z0tjYOG7cuB49eqSkpCBn6iEWsXblypW9evUKCQlBiMrLy5N2y9AF+/fvT0xMRP+hQ4euW7eu3RCrOr6/U4VYNCKfG1VXV6Pdq/nUnT17NikpCQfBXq2JiIiIiIjIT7iHWCIiIiIiIiKfxRBLREREREREfoMhloiIiIiIiPwGQywRERERERH5DYZYejvkN1SZabuJiIiIiIisMMTS26FlVhNtNxERERERkZXfQqyWId5SitDO/TbObv77q4Ht6tWrKSkpXbp0iYiI0JpcPJwHbZ2sVkpvtNzrxnUMC9puIiIiIiIiKy+FWGl6rcrKynCip0+fanUX46k9HIblcTqmuLh47dq1WuV3YPr06VOmTGloaGhsbNSaXLyaB8tl0hs9WUT0saTtJiIiIiIisuJDIfbXBPOi4Gq204kh9vdm+PDhGzZs0CodZblGeqMnK4g+lrTdREREREREVjwNsdOnT8/NzV26dGlsbGxERISkoJaWllmzZkVHR0dFRaHDw4cPpbNle3V1taQUnXx4FQXLf2H37t1paWkOhyMmJiY7O/vBgwdoVB0H7t27N378ePRPTU1dtmzZoEGDpP2TTz5Zs2aNlHft2tW7d28pFxUVuR1BeHscldbW1pkzZ2LwoaGhOFRpaSkaa2pqgoKCfvrpJ+nz7Nmznj177tixA2XL/uLChQsYZFhYWLdu3RYsWCCNqvnHONEnIyMDizVw4ECcUW+X5yv0jxN7Ow+AzlrpBTmCkbZDQetkou0mIiIiIiKy4kWI7dGjx6pVq5CUGhoaTp06hUYkpb59+yJflZeXIywhZ0pnVTuYv4PqSi7aqaWgVzdt2oR0V1tbW1FRkZ6ePmPGDGkHy+/EZmZmDhkypLKy8ujRo8hvHoZP88+Cduw4Zoj6ffr0uXjxYn19/d69e8+dOyftH374of7B3ZKSksjISMRRlFX96+rqkFTz8vJu3LiBqVi8eLG0q+YZ44yPj0fax/xMmDAhKytL2sXQoUMtvxPr+TyAvkZGbutoD30sabuJiIiIiIiseBFijRkGnE4nktW2bdukum/fPofD8fjxY1W7VL0KsUZFRUUIeFrF6jjNzc0hISFHjhyRKgJex0Jsh49jtnTp0pEjR2oVA8w5MqeU586dq4dMVf+CggK3yQebecY4c3NzpX3Lli39+/eXsvAwxNrMA1iukds62kMfS9puIiIiIiIiK16E2NmzZ2sVl7t37+Ihly5dkmpNTQ2q1dXVqnapehVi8ViMLSkpKSEhITY2Ni4uTtrBfJyKigq03Lp1S6p4Xh0LsR0+jhkmITIyEg/Pz88/duyY1vr8eWNjI/JhVVUVxo8nVVxcLO2q/pMnT54/f75WecFmnjHO1atXSzvGaQz/4GGItZkHwC6tZKA3Wu51gz6WtN1ERERERERWvAixC178KKZ43SHW6XQivmZnZyPsNTQ0bN++vXv37q5ev/IqxI4dO1YPnzt37uxwiLU/jiXkVYw8KysLqbWwsFBrff584sSJK1asOHToUHx8vPFZWPZHiHWbfLAPsTZh+zWFWLS40XYoaJ1MtN1ERERERERWOh5izR9nDQ0Ntfw4sbRLVaLRkydPpAqu5PLrqY3/AuITynV1dVJdv369McSaj+P28de8vDw9dE2ZMmX58uVS/uabb+xDbIePY2/hwoUIrlrl+fN//vOfycnJOTk5OL7W9DJj/4KCgtTUVCnrbOa5U0KszTyAvkw6vcW8yxK6WdJ2ExERERERWel4iIXc3FzjLxbSf7ZT1Q73798PDg5GsmppaZEIKuc1/iuwFyFt69atKNfW1iKJGUOs+TiQmZn5/vvvX7t2raSkpEePHnroWr16dVpaWmtr671791JSUuxDLHTsOGaFhYV79uy5efNmWVkZ+i9ZskTb4fpFxHh2SIlnzpzRmtT95Rc7LVq0qLq6uqqqSg/SqnnulBALqnkA89Wit5h3WUI3S9puIiIiIiIiK68UYpubm3NycpCvIiMjcRD5Ezg27WLdunVxcXE4nR6ZjKc2lvfv35+YmNirVy/kLjzKGGLBfJzGxsZx48Y5HA5EzS+++GLw4MHSjsSLPjExMenp6QUFBXqo69evHx5uJB/H9fY4KkjgAwYMQFLFVGBCmpqatB0uaHE7gk1/JFWcOiwsDGefN2+eNKrm2dsQ6+08APpopRf0FvMuS7+exoq2m4iIiIiIyMpLIVbIjjdMO3fnnR0pbsKECVrlFXTWccwQNf/yl79oFZ+nz4O2Tq+8UtpRTLTdREREREREVn4LsQGgtLT00KFDTU1NV65cSUxMlI8id0BnHUfll19+OXDgQHBw8LVr17Qmn/Ra50HLrCbabiIiIiIiIisBFWJPnDiRnJwcEhISHx+/atUqp9Op7fBSZx1HZfDgwVFRUZY/mOpTXus8aJnVRNtNRERERERkJaBCLPkRLbOaaLuJiIiIiIisMMQSERERERGR32CIJSIiIiIiIr/BEEtERERERER+gyGWiIiIiIiI/AZDLBEREREREfkNhlgiIiIiIiLyGwyxRERERERE5DcYYomIiIiIiMhvMMQSERERERGR32CIJSIiIiIiIr/BEEtERERERER+gyGWiIiIiIiI/AZDLBEREREREfkNhlgiIiIiIiLyGwyxRERERERE5DcYYomIiIiIiMhvMMQSERERERGR32CIJSIiIiIiIr/BEEtERERERER+gyGWiIiIiIiI/AZDLBEREREREfkNhlgiIiIiIiLyGwyxRERERERE5CeeP/9/W9vwuyLs3ScAAAAASUVORK5CYII=" alt="graphic" title="graphic" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgYAAABcCAIAAACa1+pPAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABTnSURBVHhe7Z2JcxRFG8b5OwwBEjmCKeSUAsQDpESCgBrAGDwQCxVUkMMDFNFwFFWWWCqIpYgKBQjhlMuIhuvTAhEwHkWBpUS8LRWhRBCUfI/bL83s7HRvz+7sJus+v5pKdff79rs9M9399Oxutpt99+03PHjw4MGDBw5IwlEePDJ6NDRrxoNHnh++QdFkj2bffXOUB4+MHr6xwYNHHh6+QdFkj2b/27mNB4+MHr6xwYNHHh6+QdFkj2YNhGQaPTAIyTdyrfNzlJLMk2ujgpDIyLXOz1Gaw1x00UWSioJoo8WRa6OCkMjItc4vDcV0oFDZJoW0rEm2rXHxXRPLJYpdPyekggcxWK+/tga75dqoICQycq3zX5AElWgUtmzZsnjxYsnE421YCo20RE6f4cOH33bbbZJpDHBBEhGbAxE6a2uwW8ZGRST3N9R1UDTW6zqyc+dOBD948KDKvvDCC61atVLp1MjoOAqkvLwcp6A4e/aslOYiGev8GaJJSML48eP79esnGQ+JrQrbTlPkSJg8efKUKVMk0xiEuho+57DX1tEa7JaxURHJ/bWfWiCN9bqORC4JGR1Hgfzwww9HjhyZM2cOToSSkE1yQxKQUGld4kj2u3IWUFcjEPEIQjySId4JmEyqlg+xaTI2KtK/vwGtdaCxXteR/4AkKF5++WWcCCUhm7hKwhdffAGfTZs2Pfnkk5deemmLFi369u37559/Kiu6YFlZWVFRUfv27ceOHfvTTz+pcoXFip6KsD60A9L6r0roNKivrx83blz37t3RmA4dOjzwwAM//vij2JJFBtXV1T179oQbHlGXLVsGq2kIffzxx7B+8MEHkm9oKCwsjMW7KPCNo3Qiu4AqkorHVK7wWnU6sDAQizUwWhzWUbFkyRJMN7gsJSUld91119GjR1V5mvfXEdSSlIdDhw6NHDmyXbt2F1988a233orlqhjcXtd0Rl5QS1Ie7KPM0iqwfPly1etuuukmNABxfL0OnQ3DEImrrrrqww8/VCaQ0euczhkBiyRY6trPCGBGGjhwIGYknN0111yDASuGGElbFQ5r52+ChJOEq6+++pFHHvn000/R25599tk//vgDprq6Olz6ysrK2traVatWde7cGXdd30W79ZtvvsGAGT16NLopEoq///5bWVWrdNuQ0Gmwa9euhx56aO3atZhSN27ciO7ep08fx8jbtm0rKChAv0HnmDdvHrQKkd0nbhV8yJAhiZKQZmQXUMWEeAThtep0YGEgFmtgtDjMo6Kqqqp58+bTp0/fsWPH22+/ff/99+NWKlM699eRwAZ/+eWXmA6uu+66zZs3b926FXe5W7duJ06cUNakr2s5I43pQllGmb1VNTU1qDhhwgRcNFRp06YNst5ehzGIC4i5b8OGDVCOyy677J9//lHWjF7nlM9IYZIEe137GUHPIAZ33HEHbhAc8BLPPfecMgGXVoXD3PmbJuEkAZopeQ8Q4dLSUq387733HjzfeecdlbVbFfY3jnTbkNDpRNT0un//fsnHMEUePnw4lOncuXMqiw6EumEn7sCPlyOJbAdVJBWPqVzhtSKtkaIw1X1ok9HHMCow3WD2nDNnjuRjJI5/Raj760hgg++55x702N9//11ljx07htXiokWLVFZhel3HMzJdKMsos7cKTwaY9XSvw4oEcby9DlndzdavX4/s4cOHVdZHqOsMTx9iOE/KZ6QwSYJLXY3vjLZv344sClXWR6jIThg6f5MlnCQsW7ZM8h569+49atQoyTQ0nDlzxjsq7FZFapKApcqCBQtQEaqOebZly5awOopN69atsY6TzPlBEokkRBLZDqqYEI8gvFadDiwMxGINjBaHYVSsWLECVbAuk3w86dxfF0ytLSkpefDBByUTY/DgwVjsSyaG6XXtZ6QwXiXrKLO3qri4eNq0aSoN1qxZgzjeXodBp5f2Bw4cgHX37t0qm9HrnPIZKUySYK9rP6PvvvsOz0zl5eV4DvC9oQRcWhUOQ+dvsoSTBF9HUXTs2BFPrJKJgefWqVOnqrTdqnCRBO9fxezZs3Fr582b98knn6B5tbW1sG7ZskXMMQIjo4fBc+7cuZKPvduDkvQlIarI7qC6pGL4sl68Jp0OLAzEJbLRxzAqXnrpJVQxPZWnfH8dCWwtZhOUFxYW4jZpMJ/61rmm17WfkSLwdRWmUWZvVZq9LqPXObUz0gRKQtK6Sc9o165dN998M1ZvKL/88svff/99Ve7YqnAYOn+TJZwkbN26VfIeMv2UoBP4q0tAt27dHn74Yck0NOzZswdWx65sX8vPnz8fXUGlwd69e2GN5CnBMXIg8HREKsTjLVduCimKd0gk0BoL4EdsXgyjwr6mTuf+JiW4nTGwtMSSBR3ey/fffy/mGKbXTfqUYHldgBeCQ+Aos7cKvW7mzJkqDTZu3Ig4jpKQ0euMRqZ2RopXXnkF1ROfEux1Xc4IQABwEfr27duhQwf9yYpLq8Jh6PxNlggkIfDTgpqaGpW1WxW4f1AOyXiwtwo3csaMGZJpaJg1axb8fTfeFHnYsGHe917hhrp6CKmvCem13htvvIGsoyREEtkFVPT9teP10enAwkBM1sBQfgyj4ujRo4nrAz3+07m/SbG0dvTo0QMHDtRzRCCm17WfEbBdJesos7dqaAzJNDTguiGOoyRk9DqnfEYKNV4Sv+Bkr+tyRppXX30VVvWJN3BpVTgMnb/JEoEkeL9TtHr16i5dupi+cZRoVbz++usFBQVYYR0+fBgvBPUWQ0LDvFmIDaKh3yMaFuPoB7D6brwpslKmiRMnYgWxcOFCVVcPofr6egzsqqqq48eP79+/H4MBVj2Efvvtt9jXLv79xhEEQKWPHTumrOlEdge1TAkTXofAWvYIJmtgKD/mUYEnKlwQ/N25cyd61+TJkzdt2qRM6dxfO/YzRai2bdvecsstWGvj1qxcufLee+9dtWqVmGNYXtdyRvbXBZZRZm/Vu+++i/a8+eabf/31F6xY6iKOoyRk7jqDlM9I8dVXX+Fijhs3DqNp3759Z86cUeX2uvYzwnoUzmvWrNm9e3d1dXX37t0HDRqkTMClVeEwd/6mSQSSAHbs2FEW+8pzSUnJmDFjfKput4LTp0/jrqvvawKfgypUSFEMzKqIhkfmNm3ajBgxQs3Fvq5siYyb3aNHD7QKy6ulS5fC6v0C8pIlSzp16lRUVFRRUbFu3TpY9RCaNGlSLFgcTzzxhLKClCM7giqSSkgrJB+P2KyIaxAmqy63VTePCjxOYbrBKgGXCxPZnXfe+fXXXytTmvfXAjwlZQDzAlaLl1xyCVYzmDLGjx/vey/I8rqWM0r6uvZRZm8V5uuePXu2bNlywIABzz//POI4SkLmrjNI54wUy5cvx/yuXtf7Bo6lrv2MDh06hJuCMVhYWFhaWnrffff5TselVSEwd/6myQVJUKhsvoExXFxcrNcgERJhZLlD8ffInvViMSlMDihXSD4Fcm1UEBIZudb583SUnjx5csaMGdu3b8dKCk+IWDJ4v8OXDpmLnMPk2qggJDJyrfPn6Sg9depUZWUlHhvx8Ni1a9fZs2f7Pt5ImcxFzmFybVQQEhm51vk5Sknm0aOCB4+8PXIESgLJPL6xwYNHHh45AiWBRIPt82ff2ODBIw+PHIGS8N9BfS9IIUXOSLVUv1aUcsVGIbdaS0g2iZOEpUuXXnvttUVFRe3atausrPzss8/EkDZbthg36jtw4MCNN95YXFxcWlo6adKk48ePiyFGTU3N9ddfjybBoX///k8//bQYMkkubvKHpkoqhi9rx+scqqLCUuXgwYOw+kDXEvN5Av8PPB2S3kGUS4oQ4uGCJFRVVRUUFGBS3rx5c3V19ciRI1977TWxpc14w2+kHD16FPJTVlb21ltvLVq0CGnMDmI7/1Mto0eP3rBhw6ZNm2bOnJnaf9WHJec2+UtngkusGyqa3VlJAjoVbqVmz549Yj5P5FuWutzBUKdJSJ4gklBXVwc9mDVrlsoqTp48Kam0MUnC448/3qJFi59//lll1W/+6J/txfPBkCFDVFpx6tQpSWUe0w/zNkHSmd0S64aKZndWkpDWz82ngf0OhjpNQvIEkYSpU6c2b978l19+UdlELJvPqX+X37ZtW9++fVu2bHlV/DZ+MGHs+dD/QX7FFVcMHjxYpQEWd7DqXw3r2rXr3XffrdIpoP6ZPh82+UM7JRWexLru0ZJ6JpWEQvOWpfZ+lfQ6g6SiDqukCCExRBLKysp69eql0ol8ad18DkMXclJRUQG3b7/9try83LuNn2WjPvzFjODbngKzgJYBTBOYOtesWXP69GlVEgolCfmwyZ+a2vBXoQrd8VYJVT2ps5KEF1988bgH74+mqe6BMw2UBEu/sl9nBSWBkLCIJGCweVfrPuybz2HoYmhh8Kss5jJkD8dv4xf4xhEmO3hivpZ8DLwQZgGVxhr5yiuvhA90YtCgQQsWLNBrfBeUJARuf/Ef2+QPMYFkUprpVAQgeTeS+itJ8JH4WULgx8su/Urju84KSgIhYXGSBPvmcxi6WOzrHQJ82/gpUpMEgBVlbW3tU0891b9/fzhjye+yalYoSciHTf7wEpI6T2KJBa9zyhUDUZIwbdo0rOU1iXfQJAmWfmW/zgpKAiFhEUkYMGCA6Y0jjD2MHAxODDwNnuj16htDFyUqDXw/vasIlAREtr9x5GPt2rWIjHEu+WQoSciHTf5QUVLnSSwxEW1dH0oSkj7omCQBV0AyCf3KZXtISgIhYRFJePTRRzH7/PrrryrrAwsxy+ZzKUsCwJzofTrxfbycSNu2bdFUySQDjUS0rUG/1W4/I0UObfKHl5DUeRJLTERb10fmJMHlOpvuoCZp+wnJN0QS9u3bh+Hh+xKq4+ZzLpJg2qhvypQpWOuZvoRaX1+vEgpMjlhEP/PMM5JPBuZTRAuUBPsZKXJokz8EkdR5EktMpFMX2J0zJwku19l0BxX2lhOSn4gkgMcee6ygoGDChAkbNmxYsWKF91/VDls3n3ORBNNGfZj0W7duXVZWtnbtWqzpsEwuLy9XJnDDDTcMHToU8yaGOiL06tULzj6dsGCRBPsZKXJrkz/vBJc42aEksVDjNVncArH72yXBvmWpvV+5bA9puoOKsGdKSD5wQRLOnTu3ePHifv36FRUVYWKqqKj4/PPPxWbdfM5FEiwb9X300UeYEfCiCD5x4kT9fRuAmXHEiBEY7Xg4QN3bb7+9rq5ObA5YJAFYzkiTW5v8qXYCyXswlWuUA5B8GCy17JJg37LU3q/s11ljuoPISooQ4uGCJBCSMrk1w1IPCDFBSSDRkCvzLPWAEAuUBEIIIQIlgRBCiEBJII3DRVbEiRCSXSgJOUy0U2eWJ+LYzG9EnAgh2UUkQQZikxyK0jJOEwn4ronlEsWunxNSwYMYrNdfW+1uXmIhjYgTISS7XJAElWgUtpi34fQ2LIVGWiKnT+D/3GaTf+fOBMTmQITO2uoeE54WxIkQkl2ahCSYfgEpsVVh22mKHAmRbw8ZllBXw+cc9to6Wu1uXuBpQZwIIdklNyQhNkv8m9YljmRUEhoLdTUCEY8gxCMZ4p2AyaRq+RCbFXE1IE6EkOziKgnqxyFMm1bu3LmzrKysqKioffv2Y8eO9f1Ig8XayroNJ9L6r0roNLBvtWiPDKqrq3v27Am38vJy9ftoB8/v1pL0JzoKzdtDgnQiu4AqkorHVK7wWnU6sDAQizUwWlLgbEGcCCHZJZwkBG5aWVdXh0m5srKytrZ21apVnTt3hlroXyS2Wy3bcALVKt02JHQa2LdatEfetm1bQUEBFAVyNW/ePPXjS+4Tt2V7yDQju4AqJsQjCK9VpwMLA7FYA6MlBc4WxIkQkl3CSULgdi5YnpeWluonBvUDZHrjGrtVYX/jSLcNCZ1ORE2vvq0WTZGHDx8OZdI7dkFaUDfsxB348XIkke2giqTiMZUrvFakNVIUproPbbJH8AFnC+JECMku4SQhcNPK3r17jxo1SjINDWfOnGnevLneBsduVaQmCVjyJ91q0RS5devW06dPl0xDw/r161E3EkmIJLIdVDEhHkF4rTodWBiIxRoYLSlwtiBOhJDsEk4SEjetBB07dpwwYYJkYrRp02bq1KkqbbcqXCTB+1fhstViYOSzZ8/Cc+7cuZKPvduDkvQlIarI7qC6pGL4sl68Jp0OLAzEJbI9gg84WxAnQkh2CScJgXsPZPopQSfwV5cAl60WU3tKmD9/vnfi3rt3L6yRPCU4Rg4Eno5IhXi85cpNIUXxDokEWmMB/IgtGeJtQJwIIdklAkkI/LSgpqZGZe1WhWkbTnurXLZaNEUeNmxYnz599Dv+cENdPXGrrwmdOHFCZdX2n46SEElkF1DR99eO10enAwsDMVkDQ7kAfwviRAjJLhFIgvc7RatXr+7SpYvpG0eJVoVpG07ga5g3C7FJutWiKbJSpokTJ+LZYuHChaqunrjr6+vxKFNVVXX8+PH9+/dDVGDVE7d9e8h0IruDWqaECa9DYC17BJM1MJQL8LcgToSQ7BKBJIAdO3aUlZW1atWqpKRkzJgxvv9LsFuBZRtOoAoVUhTDZatFS+SVK1f26NEDrRo6dOjSpUthPXLkiNgaGpYsWdKpU6eioqKKiop169bBqidu+/aQIOXIjqCKpBLSCsnHIzYr4hqEyarL7dUT+ff1zIgTISS7XJAEhcrmG3iYKC4u9m3XHgkRRpY7FH+P7FkvFpPC5IByheQjQoIaECdCSHYRScg3Tp48OWPGjO3btx88eHDjxo1Ytk+bNk1s6ZG5yP8xZO43IE6EkOySp5Jw6tSpysrK0tLSwsLCrl27zp492/fxRspkLvJ/DJn7DYgTISS75KkkkEZH5n4D4kQIyS6UBEIIIQIlgRBCiEBJIIQQIlASCCGECJQEQgghAiWBEEKIQEkghBAiUBIIIYQIlARCCCECJYEQQohASSCEECJQEgghhAiUBEIIIQIlgRBCiEBJIIQQIlASCCGECJQEQgghAiWBEEKIQEkghBAiUBIIIYQIlARCCCECJYEQQohASSCEECJQEgghhAiUBEIIIQIlgRBCiEBJIIQQIlASCCGECJQEQgghAiWBEEJIjIaG/wPuYPankM24UAAAAABJRU5ErkJggg==" alt="graphic" title="graphic" /&gt;&lt;/p&gt; &lt;h3&gt;Docker的基本组成&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;镜像(image)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Docker 镜像（Image）就是一个&lt;strong&gt;只读&lt;/strong&gt;的模板。镜像可以用来创建 Docker 容器，一个镜像可以创建很多容器。&lt;/p&gt; &lt;p&gt;&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjYAAACHCAYAAAAFvxPMAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABKsSURBVHhe7ZzNbtxGFkb9TnqWvIQR6zkCJIDgNwi8CZBspAdwljGQjWDYa3ujjQEtZmHMllO3fm/9kt0qa9jUOcCZqPlbLJK3PrLb82oBAAAAOAgEGwAAADgMnWDzcbm5ulqu7x795wb3N8uVWSZ5vdx+W5bHu+ti+o3ZWs7Ht1fLzb3/sMa32+X6ze1StUT235oeeVxu37g2ub9NW96WLSloblNvp0GvfQbpi83HGXF9X/ab9FlqW9kmt061Lzme7JjNcllb88/1uevp911dAysO+ml4rW1ktB17bJ39lzS3I+c5HHcHe47sMuEclq6tP55vOecaXWFWvwkn3duGs65rAIABJtj0inDbWACzQTMVJCmEqRDJtstgc1oRzovuqK3FNlXgcANOYSPkNItyFQ5qmsXfDoR1qLPLlm3JrNeRPnv8Jv1m5tu26D70fWKmyzKa2HfSFn8M1flZGbTy/t/AIOi12XA92L7UfRT6Iafb1m4Aa+1XXbMrwS32rf5saffrWnDZEmxOukaftd+E0+5tWf6c6xoAYIQPNq3BtCYrgFXRcwVJlsmnt4KNnt9TtlcsWxU/v79GsQ4DQP5EKMjxlsXXF1GtX6cVilqDgCyXprt2VwNQSRh4Gu0fk/dBG7WM7CfuQw+6+u823UGvQ3PgLaivkZ6dY/QDeevcVOtlxx5w56d1XNK22P5eYDCU/ZJ/bvfr04LN06/RH9lvDje/vW1t7zi3XNcAAGPyYOPDii5crgi6YpMV76zop4LkBgZ5EpPpatuR9cE0Ugwssu26SCr1gC379ccz0h1P2U7fRinuRVt1H6y2J1oWa9mfmd7oB7dNtXwIP1sN/WXX88dUDFLuHJk/GsdXoo+3RX+QLE3HFPc/RA9y+TXTa1M5XT6321Lr2hP24wbom7fj60f21drHzb0/v5WpD07vN9nmqdfoc/VbIN/fkHOvawCAFexvbGIhi8XDFeZUtMKTmBpwG0ixjgNErxgVg+yIx7sbU7R9YbP77j3Ryf5S0XfH0xrU6+XiAFANGjfGel/lIFAi84eD9lpBX+sbG/ZubT+4vtHtTriBs9UHCpm+MhCtHW865/Wx63n6bcRqH1nyc63bEbZbti37bPrp2gST6viKsCzEdqogbLfTWLaHbGPUTyW6bwKtNzZp2nnX6LP0W+CEe7vCbn/9ugYAWKP68bAUuRBgpHC13iq4IusGgG2q4qsGj766WOuCHgJWSzWIS3HUn5vLO11Bbw0a7rjL4l0OCiUyvxywxvi+tANCeNNVY8+FWs6+Ubgv+iMMKq0+aA04arBy511ta9V0jYTjbW0jzGsHm/b5TPP0daCPWV8P+Xazc7Ny7rXpnH1cbtUgf2UG+e61HvvUnUPpc9nOsC/9OlUoMJwebLZco8/Vb4aT723Xdjvd9otuq1qnde0CAHSIwSYU42rQDkVuVFx8QYsF6UmFKBXwWPSC/omuLP56neXeLGP/VUoxCPhtZIX4m3zpIKT50Rjo3DGFfomDwAnF31oExDT46Xb6Y9b9F/vWf7bkA5PD973sx6zjvg7x2+4Em2pAa7C2jB5UZVndTj2vH2zy4xjNi32u+9KGD3dso7bKvNZ5EPSPU9M1Z/Z954ONrBP6sPyvYNogoebKWP5gvNcm3TeB9WAT2uaNx9K5RgPP0G/bUNfkOde1nwIAMOJVKDDbNYWpXCcU+EC1zbzYj1HFz6I/+yKXbTvYWCe2w81zRdr9HQYwV9Ab+8wKaSq4so1yQIr4AaQ3SFjCILNm2P+p5ye2Wx1TMQi7ZVqDSM1o0BPKYFO2J8ybEmwM9rxl15ssl85vbGvRb+k8y2dZXtbz80x/uC26+WEbbnu3K8HG7f827NvsV/dX1iZFClBrhj542jX6Y/ttK+oYzr6uAQDGpK+ipNBkhU9TFlFDVZhSUc2np+KZT29plpUBI9uXLui+eN+HZcI8vYxQfPZtlSKd2qEHzcb6nULaGjQcruDLPD3Yb6LR97adnfNh55l+qH4HkaGOSZ+rsI6dpvugjeyrNTAHymCjj1vP6webcO6TaV7RPmmzGYxbvysRem21/WW37frDhQrpv/Hxu/ZLP+bti8r58ecu27fu78LUH+nvwPobmzOv0R/cb2m5kWYb1b2dY7djtj2+rgEAxjwt2MTl0yCUF9WyGG+gF2ziE7IvgPbv3vb99DDA+PVcYfafZXtx3XI75nOnuLYGJHf86Uk/fK6XCxTLx76UdrQHn4g9Jt9W+bs7CKRjcgNG2admPzEg9pF1W4NeQPdHfu7zeVvf2CTKedJmv73OcWdttefXne+sn2Ubd/64mwOtOzdxMJb9hmXDdaP/a7/6LPatWOs/TSvYJNL5dPh2+U+a/Bp9rn7bwGg9u4/161rams4NAEBNHmx8QWtaFppqeVeUU+EJnlaA7Pp2kA+4wpwVvbhNGYT032FgyAeB0CZb3HVxtUVclgv7UDYLqxv00qBRbLvABanUDosfOLLBTo4pHrNvS7e/29ur25v3QcQvH9or7R8NvGvz3TFuMQ3ass1Wf+Xo8+n6XbejFQLabXXr2jaEPpa+9P1lz1/oO9vH6jq2y5t+lPnSb/K5/K+n10+96S3Wg43uT2N1zgV9jT5Tv20k9anipOta2pTOz/o1BAAvkf/zGxtVPKNF4Y3F1Bf2oh35wBr25ffri6MUbb1cXRDLdqrjDQU2GKb7grw+aPl2y/btOmk/ozaFQcD+N6zv5zWJ7Qz9Vx6TwS5TDmxyDorl/LE5y+Vz5Bi2DDDtNzYNsn37dsm03rXZWl6I0/1+s+WKNtt5+jjT9Wz7Mbvm0jWrz70ck/scznfPfn+e/cZmdI0+a79pxvf2udf15vUA4MWSgg0AAADAhUOwAQAAgMNAsAEAAIDDQLABAACAw0CwAQAAgMNAsAEAAIDDQLABAACAw0CwAQAAgMNAsAEAAIDDQLABAACAw0CwAQAAgMNAsAEAAIDD8Oqnn35aEBEREY8gwQYREREPI8EGERERDyPBBhEREQ8jwQYREREPI8EGERERD2Mz2Lx/8P9mSvP90/KusexJ/v5p+S6b+vyuPR8RERHxCQ6CzcPyPk57bz5NCDcEG0R8Kf7dekLsoeut19fL5eF9Pj3Y2n5Ro999thV3+fS7Wi9ON/S2jXjBbgw2Rn8TPfwdpvmw48nDyrvlk79vLOHmyYJNWD/tJ95sQrxB/ba+PywPdnZ9kyIi7t61oFLqa+62B0FfTx9MnbRrlaS6mdXZEoIOHsDtwaYVSsJNUIQeu34IJnqe2obbhwop2Tb09kNIItAg4mXp6tw6Wd3bQqyNoS66dcsQlL+xUQ+cWYChxuKxPC/YVG9vwlsVCTPtG0xvI5DWr/eZwpHedloeEXHvxjrma1+oee3pPtgMa5166Av1NNuODjtFsPF1ewvN+o14IZ4VbPKnAFGFD7VcXLfYRiTewOGJoUTaQLBBxMu0DDAZWSCR5VWwaYYQqYcq2Mg+/HLfTZgx/7t8+tvvx88va3V6YHSfw9dS6SFz8GCKeCGe9xubDW9s8led3nBzm3nlDdXcp5Vgg4iXaRlssnpXTa+DTV0fy/qqHgp9jdRhphlsNkCwwUt2Y7Apw0X7qaEZUvyNa28U/be+iatt6O0TbBDxMi0DTIaafn6wMfpl47SwL/O5DDbZ/PAPMkJtjW1sPWAiXo6DYFNQBQt/g3nCDehUTxFCccPFp4Hi5g1vcSxxfwQbRLxMy2CTBZVqeh1sclbe2Bj09h9MnX2vg03Ypq2lbjvfP3/K6rib9860y20H8RJtBhtERHy6Tw02YflkEWzicn66+V/9tiV7Y5OFJbOc37fDfPbz+RoKL12CDSLiD9IGmA1UwaaxLasPI9nX+T7k2BDzIGEpLe/23wo7DtmvDj+xvfGNEOLlSbBBRPxBxjczjXnW3hubxjIJH1TkDUtz2+Htjad4u+PW18vkv8GRNj+YdfidDV6qBBtEREQ8jAQbREREPIwEG0RERDyMBBtEREQ8jAQbREREPIwEG0RERDyMrz5//uz/yR8AAJwLtRRgHxBsAAAmQC0F2AcEGwCACVBLAfYBwQYAYALUUoB9QLABAJgAtRRgHxBsAAAmQC0F2AcEGwCACVBLAfYBwQYAYALUUoB9QLABAJgAtRRgHxBsAAAmQC0F2AcEGwCACVBLAfYBwQYAYALUUoB9QLABAJgAtRRgH5wYbD4uN1dXy/Xdo//c4P5muTLLJK+X22/L8nh3XUy/MVsDADgGTw02UiN7tdXWzze3y6DyAoBnEGxciMnDSN94Q0qweRsiy+Ny+yYFm5t7P9lum2ADAMehW0u/3S7XZc2MNTLRDTbVw2LQ1VYAyFkJNtvCR3ZD8sYGAF4gm9/Y+Ie/j291PdSqwCKhqApB8sC48uYc4AWzLdj4sKJvJHdTptCy7Y3N4/Job1je2ADAsRjWUvU1Uu/NTDm9fhjsm96GA8DwNzbxxopBRQKJvonck8PaK1EJQW4dt37rNSwAwCWzVktDaAn1sBVk9APi9Vtj+bua7MHRkeorAAibfzzsQo4LMPZtTfOHbD64bHYciAAALoVxLXUPgfLW+vZNeFud3mjbT0XQaf42pyPBBiCxGmxcoGl8nxtuutHbF/8VlruZV5YFALhgVh8SQ83UD4XqDUwVbBShDrceKB+/tdcBeKn0g40PJds1TyHlOmWQqbbJ72wA4Bhseftt33ZndTG9wcmCTVEr3fTwRlyW9w+LMs+EHeooQGL9qyj1RFGT/yjOUoWX9OPhfDqhBgCOw6ZaakLMjfr6SdN7Y5Nqp6uZNhyZunrd2Q7AS+fHBJu4fPoOWW7O9D2wPHkQbADgOIxrqXvbYmugDTiNr5R0sFG/rynf4tzcmXmyvizT2A7AS2dbsPE3WFPe2AAADIKN+9pIv435+LZ+29J+Y5O+cooPjCoY2bpKuAHI4I0NAMAEurV0VEOzB0FVE+N0H4Cy5Yp/BWXn1UEJ4KWyHmwAAGAVainAPiDYAABMgFoKsA8INgAAE6CWAuwDgg0AwASopQD7gGADADABainAPiDYAABMgFoKsA8INgAAE6CWAuwDgg0AwASopQD7gGADADABainAPiDYAABMgFoKsA8INgAAE6CWAuwDgg0AwASopQD7gGADADABainAPiDYAABMgFoKsA8INgAAE6CWAuwDG2wQERERj6ANNv/9/h9ERHyC1FLEfUiwQUScILUUcR8SbBARJ0gtRdyHBBtExAlSSxH3IcEGEXGC1FLEfUiwQUScILUUcR8SbBARJ0gtRdyHBBtExAlSSxH3IcEGEXGC1FLEfUiwQUScILUUcR8SbBARJ0gtRdyHBBtExAlSSxH3IcEGEXGC1FLEfTgONl//WH5+/cfyUE7/8Mty1Zoe/bL8+fr18ufX8PfVcvXbP8UyiIjHcS3Y/PubqYOxbuoaKf6z/Hp1tfz6IS2PiOc5DDYPf71efv7ri//sbryrpvoGNapAZG/mcnlCDiIezHGw+bI8fNUPeTrY+Npqpssy7fURcauDYONvwiyM5E8ZEnxaIUXCjDx55E8ootzARQhCRDyAa29scss3Nog4y36wka+bVGixISaEnJYxwEh4+WX5V9ZvLadMb4MQES/bbi2VN9iN+te18bCIiNvtBpuHv35Zfo5vbOTJoveE4YNMXE8CkP8sN3S8SevlCDaIeBQ3v7GxD41/2Hrqamyqi4j4dMc/Hs7CSPHVVKYKMnKz6s/N5Z0EG0Q8imvBJv7eMH6tL1/ZF3WVtzWIT7YbbKof/fonjOEbmw9mma/5mxk3320j+8X/1y+Df1WFiHhZdoON/1o+/xdPrTfgPuQM/8UpIq45942NXif+xsbN019RhdDEGxtEPIprwWazBBvEJ3lisDFPGB/CP+UO8/Qy5TpGf1NLiEk/QG69+UFEvFzHtTTX1kL7GxtCDOJstwUb9SNge0Pav8tAU6wTnlL8etn3y/a3N611EREv083BxtZGX//kb8IN4lQ3BBsJJOomjIFE3uDov8NbmDzwhLc09vtl++NifxMTbhDxQK4Gm/CwV9a98I8sCDiIUxwHm/g04QNOcePlPzAON6sPNv5mla+g9HL8X4Yj4hHt1dL0FfzKg1wIOHxVj/gkV97YICLiFqmliPuQYIOIOEFqKeI+JNggIk6QWoq4Dwk2iIgTpJYi7kOCDSLiBKmliPuQYIOIOEFqKeI+JNggIk6QWoq4Dwk2iIgTpJYi7kOCDSLiBKmliPuQYIOIOEFqKeI+JNggIk6QWoq4Dwk2iIgTpJYi7kOCDSLiBKmliPuQYIOIOEFqKeI+JNggIk6QWoq4D22wQURERDyCrxYAAACAg0CwAQAAgMNAsAEAAICDsCz/A5f09iWTG6n8AAAAAElFTkSuQmCC" alt="graphic" title="graphic" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;容器(container)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Docker 利用容器（Container）独立运行的一个或一组应用。容器是用镜像创建的运行实例。&lt;/p&gt; &lt;p&gt;它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;可以把容器看做是一个简易版的 Linux 环境&lt;/strong&gt;（包括root用户权限、进程空间、用户空间和网络空间等）和运行在其中的应用程序。&lt;/p&gt; &lt;p&gt;容器的定义和镜像几乎一模一样，也是一堆层的统一视角，唯一区别在于容器的最上面那一层是可读可写的。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;仓库(repository)&lt;/p&gt; &lt;p&gt;仓库（Repository）是集中存放镜像文件的场所。&lt;/p&gt; &lt;p&gt;仓库(Repository)和仓库注册服务器（Registry）是有区别的。仓库注册服务器上往往存放着多个仓库，每个仓库中又包含了多个镜像，每个镜像有不同的标签（tag）。&lt;/p&gt; &lt;p&gt;仓库分为公开仓库（Public）和私有仓库（Private）两种形式。&lt;/p&gt; &lt;p&gt;最大的公开仓库是 Docker Hub(https://hub.docker.com/)，&lt;/p&gt; &lt;p&gt;存放了数量庞大的镜像供用户下载。国内的公开仓库包括阿里云 、网易云 等&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包好形成一个可交付的运行环境，这个打包好的运行环境就似乎 image镜像文件。只有通过这个镜像文件才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件，可以生成多个同时运行的容器实例。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;image 文件生成的容器实例，本身也是一个文件，称为镜像文件。&lt;/li&gt; &lt;li&gt;一个容器运行一种服务，当我们需要的时候，就可以通过docker客户端创建一个对应的运行实例，也就是我们的容器&lt;/li&gt; &lt;li&gt;至于仓储，就是放了一堆镜像的地方，我们可以把镜像发布到仓储中，需要的时候从仓储中拉下来就可以了。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;strong&gt;Docker架构图&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516151927579.png" alt="image-20210516151927579" title="image-20210516151927579" /&gt;&lt;/p&gt; &lt;h3&gt;安装步骤&lt;/h3&gt; &lt;h4&gt;CentOS6.8安装Docker&lt;/h4&gt; &lt;ol&gt; &lt;li&gt;yum install -y epel-release &lt;code&gt;Docker使用EPEL发布，RHEL系的OS首先要确保已经持有EPEL仓库，否则先检查OS的版本，然后安装相应的EPEL包。&lt;/code&gt;&lt;/li&gt; &lt;li&gt;yum install -y docker-io&lt;/li&gt; &lt;li&gt;安装后的配置文件：/etc/sysconfig/docker&lt;/li&gt; &lt;li&gt;启动Docker后台服务：service docker start&lt;/li&gt; &lt;li&gt;docker version验证&lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;CentOS7安装Docker&lt;/h4&gt; &lt;ol&gt; &lt;li&gt;官网中文安装参考手册&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;a href="https://docs.docker-cn.com/engine/installation/linux/docker-ce/centos/#prerequisites" target="_blank"&gt;https://docs.docker-cn.com/engine/installation/linux/docker-ce/centos/#prerequisites&lt;/a&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt; &lt;p&gt;确定你是CentOS7及以上版本&lt;/p&gt; &lt;p&gt;&lt;code&gt;cat /etc/redhat-release&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;yum安装gcc相关&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum -y install gcc yum -y install gcc-c++ &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;卸载旧版本&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;yum -y remove docker docker-common docker-selinux docker-engine &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2018.3官网版本&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;安装需要的软件包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum install -y yum-utils device-mapper-persistent-data lvm2 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;设置stable镜像仓库&lt;/p&gt; &lt;p&gt;大坑：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 报错： 1  [Errno 14] curl#35 - TCP connection reset by peer 2  [Errno 12] curl#35 - Timeout &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;推荐：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;更新yum软件包索引&lt;/p&gt; &lt;p&gt;&lt;code&gt;yum makecache fast&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;安装DOCKER CE&lt;/p&gt; &lt;p&gt;&lt;code&gt;yum -y install docker-ce&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;启动docker&lt;/p&gt; &lt;p&gt;&lt;code&gt;systemctl start docker&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;测试&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;docker version docker run hello-world &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;配置镜像加速&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#创建目录 mkdir -p /etc/docker #创建配置文件 vim  /etc/docker/daemon.json  #网易云 {&amp;quot;registry-mirrors&amp;quot;: [&amp;quot;http://hub-mirror.c.163.com&amp;quot;] }  #阿里云 {&amp;quot;registry-mirrors&amp;quot;: [&amp;quot;https://｛自已的编码｝.mirror.aliyuncs.com&amp;quot;]} #重载加速域名 systemctl daemon-reload #重启docker systemctl restart docker &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;卸载&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;systemctl stop docker yum -y remove docker-ce rm -rf /var/lib/docker &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;永远的HelloWorld&lt;/h3&gt; &lt;h4&gt;阿里云镜像加速&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;登录https://dev.aliyun.com/search.html&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;注册一个属于自己的阿里云账户(可复用淘宝账号)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;获得加速器地址连接（镜像加速器-专属加速器地址）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;配置本机Docker运行镜像加速器 鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，我们可以需要配置加速器来解决， 我使用的是阿里云的本人自己账号的镜像地址(需要自己注册有一个属于你自己的)：  https://xxxx.mirror.aliyuncs.com &lt;code&gt;vim /etc/sysconfig/docker&lt;/code&gt; 将获得的自己账户下的阿里云加速地址配置进 &lt;code&gt;other_args=&amp;quot;--registry-mirror=https://你自己的账号加速信息.mirror.aliyuncs.com&amp;quot;&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;重新启动Docker后台服务：service docker restart&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Linux 系统下配置完加速器需要检查是否生效&lt;/p&gt; &lt;p&gt;如果从结果中看到了配置的--registry-mirror参数说明配置成功，如下所示:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516154848351.png" alt="image-20210516154848351" title="image-20210516154848351" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;网易云加速&lt;/h4&gt; &lt;p&gt;同上述阿里云，只是配置Json串的地方不同了:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;vim /etc/docker/daemon.json {  &amp;quot;registry-mirrors&amp;quot;: [&amp;quot;http://hub-mirror.c.163.com&amp;quot;] } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;启动Docker后台容器&lt;/h4&gt; &lt;h5&gt;&lt;strong&gt;docker run hello-world&lt;/strong&gt;&lt;/h5&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516155429005.png" alt="image-20210516155429005" title="image-20210516155429005" /&gt;&lt;/p&gt; &lt;p&gt;输出这段提示以后，hello world就会停止运行，容器自动终止。&lt;/p&gt; &lt;h5&gt;&lt;strong&gt;run干了什么？&lt;/strong&gt;&lt;/h5&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516155530174.png" alt="image-20210516155530174" title="image-20210516155530174" /&gt;&lt;/p&gt; &lt;h3&gt;底层原理&lt;/h3&gt; &lt;p&gt;Docker是怎么工作的？&lt;/p&gt; &lt;p&gt;Docker是一个Client-Server结构的系统，Docker守护进程运行在主机上， 然后通过Socket连接从客户端访问，守护进程从客户端接受命令并管理运行在主机上的容器。 容器，是一个运行时环境，就是我们前面说到的集装箱&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516155649025.png" alt="image-20210516155649025" title="image-20210516155649025" /&gt;&lt;/p&gt; &lt;p&gt;为什么Docker比VM快？&lt;/p&gt; &lt;p&gt;(1)docker有着比虚拟机更少的抽象层。由亍docker不需要Hypervisor实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。&lt;/p&gt; &lt;p&gt;(2)docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。仍而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,返个新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返个过程,因此新建一个docker容器只需要几秒钟。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516155745607.png" alt="image-20210516155745607" title="image-20210516155745607" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/image-20210516155758828.png" alt="image-20210516155758828" title="image-20210516155758828" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 16 May 2021 08:23:00 GMT</pubDate>
    </item>
    <item>
      <title>Rocketmq--消息驱动</title>
      <link>https://maruifu.cn/article/191</link>
      <content:encoded>&lt;h2&gt;&lt;strong&gt;MQ简介&lt;/strong&gt;&lt;/h2&gt; &lt;h3&gt;什么是MQ&lt;/h3&gt; &lt;p&gt;MQ(Message Queue)是一种跨进程的通信机制，用于传递消息。通俗点说，就是一个先进先出的数&lt;/p&gt; &lt;p&gt;据结构。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.10.15-20210516110622520.png" alt="2021-05-15-11.10.15" title="2021-05-15-11.10.15" /&gt;&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;MQ的应用场景&lt;/strong&gt;&lt;/h3&gt; &lt;h4&gt;&lt;strong&gt;异步解耦&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;最常见的一个场景是用户注册后，需要发送注册邮件和短信通知，以告知用户注册成功。传统的做法如 下:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.11.17-20210516110727506.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;此架构下注册、邮件、短信三个任务全部完成后，才返回注册结果到客户端，用户才能使用账号登录。 但是对于用户来说，注册功能实际只需要注册系统存储用户的账户信息后，该用户便可以登录，而后续 的注册短信和邮件不是即时需要关注的步骤。&lt;/p&gt; &lt;p&gt;所以实际当数据写入注册系统后，注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返 回用户结果，由消息队列 MQ 异步地进行这些操作。架构图如下:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.11.38.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;异步解耦是消息队列 MQ 的主要特点，主要目的是减少请求响应时间和解耦。主要的使用场景就是将&lt;strong&gt;比 较耗时而且不需要即时(同步)返回结果&lt;/strong&gt;的操作作为消息放入消息队列。同时，由于使用了消息队列 MQ，只要保证消息格式不变，消息的发送方和接收方并不需要彼此联系，也不需要受对方的影响，即 解耦合。&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;流量削峰&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;流量削峰也是消息队列 MQ 的常用场景，一般在秒杀或团队抢购(高并发)活动中使用广泛。在秒杀或团 队抢购活动中，由于用户请求量较大，导致流量暴增，秒杀的应用在处理如此大量的访问流量后，下游 的通知系统无法承载海量的调用量，甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问 题，可在应用和下游通知系统之间加入消息队列 MQ。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.12.12.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;秒杀处理流程如下所述:&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;用户发起海量秒杀请求到秒杀业务处理系统。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列 MQ。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;下游的通知系统订阅消息队列 MQ 的秒杀相关消息，再将秒杀成功的消息发送到相应用户。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;用户收到秒杀成功的通知。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;&lt;strong&gt;常见的MQ产品&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;目前业界有很多MQ产品，比较出名的有下面这些:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;ZeroMQ&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;号称最快的消息队列系统，尤其针对大吞吐量的需求场景。扩展性好，开发比较灵活，采用C语言实 现，实际上只是一个socket库的重新封装，如果做为消息队列使用，需要开发大量的代码。ZeroMQ仅 提供非持久性的队列，也就是说如果down机，数据将会丢失。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;使用erlang语言开发，性能较好，适合于企业级的开发。但是不利于做二次开发和维护。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;ActiveMQ&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;历史悠久的Apache开源项目。已经在很多产品中得到应用，实现了JMS1.1规范，可以和spring-jms轻 松融合，实现了多种协议，支持持久化到数据库，对队列数较多的情况支持不好。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;阿里巴巴的MQ中间件，由java语言开发，性能非常好，能够撑住双十一的大流量，而且使用起来很简 单。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Kafka是Apache下的一个子项目，是一个高性能跨语言分布式Publish/Subscribe消息队列系统，相对 于ActiveMQ是一个非常轻量级的消息系统，除了性能非常好之外，还是一个工作良好的分布式系统。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;RocketMQ入门&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;RocketMQ是阿里巴巴开源的分布式消息中间件，现在是Apache的一个顶级项目。在阿里内部使用非常广泛，已经经过了&amp;quot;双11&amp;quot;这种万亿级的消息流转。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;RocketMQ环境搭建&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;接下来我们先在linux平台下安装一个RocketMQ的服务&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;环境准备&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;下载RocketMQ&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;http://rocketmq.apache.org/release_notes/release-notes-4.4.0/&lt;/p&gt; &lt;p&gt;&lt;strong&gt;环境要求&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Linux 64位操作系统 64bit&lt;/p&gt; &lt;p&gt;JDK 1.8+&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;安装RocketMQ&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;1 上传文件到Linux系统&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# ls /usr/local/src/  rocketmq-all-4.4.0-bin-release.zip &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 解压到安装目录&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;  [root@maruifu src]# unzip rocketmq-all-4.4.0-bin-release.zip  [root@maruifu src]# mv rocketmq-all-4.4.0-bin-release ../rocketmq &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;启动RocketMQ&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;1切换到安装目录&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# ls benchmark bin conf lib LICENSE NOTICE README.md &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 启动NameServer&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# nohup ./bin/mqnamesrv &amp;amp; [1] 1467 # 只要进程不报错,就应该是启动成功了,可以查看一下日志 [root@maruifu rocketmq]# tail -f /root/logs/rocketmqlogs/namesrv.log &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3 启动Broker&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 编辑bin/runbroker.sh 和 bin/runserver.sh文件,修改里面的 # JAVA_OPT=&amp;quot;${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g&amp;quot; # 为JAVA_OPT=&amp;quot;${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m&amp;quot;  [root@maruifu rocketmq]# nohup bin/mqbroker -n localhost:9876 &amp;amp;  [root@maruifu rocketmq]# tail -f /root/logs/rocketmqlogs/broker.log &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;测试RocketMQ&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;1 测试消息发送&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# export NAMESRV_ADDR=localhost:9876  [root@maruifu rocketmq]# bin/tools.sh  org.apache.rocketmq.example.quickstart.Producer &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 测试消息接收&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# export NAMESRV_ADDR=localhost:9876  [root@maruifu rocketmq]# bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;strong&gt;关闭RocketMQ&lt;/strong&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;[root@maruifu rocketmq]# bin/mqshutdown broker [root@maruifu rocketmq]# bin/mqshutdown namesrv &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;RocketMQ的架构及概念&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.21.02.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;如上图所示，整体可以分成4个角色，分别是:NameServer，Broker，Producer，Consumer。&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Broker(邮递员)&lt;/strong&gt;:Broker是RocketMQ的核心，负责消息的接收，存储，投递等功能&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**NameServer(邮局):**消息队列的协调者，Broker向它注册路由信息，同时Producer和Consumer 向其获取路由信息Producer(寄件人)消息的生产者，需要从NameServer获取Broker信息，然后与 Broker建立连接，向Broker发送消息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Consumer(收件人)&lt;/strong&gt; :消息的消费者，需要从NameServer获取Broker信息，然后与Broker建立连 接，从Broker获取消息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**Topic(地区):**用来区分不同类型的消息，发送和接收消息前都需要先创建Topic，针对Topic来发送 和接收消息Message Queue(邮件)为了提高性能和吞吐量，引入了Message Queue，一个Topic可 以设置一个或多个Message Queue，这样消息就可以并行往各个Message Queue发送消息，消费 者也可以并行的从多个Message Queue读取消息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**Message:**Message 是消息的载体。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Producer Group:生产者组，简单来说就是多个发送同一类消息的生产者称之为一个生产者组。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Consumer Group:消费者组，消费同一类消息的多个 consumer 实例组成一个消费者组。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;&lt;strong&gt;RocketMQ控制台安装&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;1 下载&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 在git上下载下面的工程 rocketmq-console-1.0.0  https://github.com/apache/rocketmq-externals/releases &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 修改配置文件&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 修改配置文件 rocketmq-console\src\main\resources\application.properties  #项目启动后的端口号 server.port=7777  #nameserv的地址，注意防火墙要开启 9876端口 rocketmq.config.namesrvAddr=192.168.109.131:9876  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3 打成jar包，并启动&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 进入控制台项目，将工程打成jar包 mvn clean package -Dmaven.test.skip=true # 启动控制台 java -jar target/rocketmq-console-ng-1.0.0.jar &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4 访问控制台&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.26.52.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;消息发送和接收演示&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;接下来我们使用Java代码来演示消息的发送和接收&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;org.apache.rocketmq&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;rocketmq-spring-boot-starter&amp;lt;/artifactId&amp;gt;    &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;发送消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;消息发送步骤:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;创建消息生产者, 指定生产者所属的组名&lt;/li&gt; &lt;li&gt;指定Nameserver地址&lt;/li&gt; &lt;li&gt;启动生产者&lt;/li&gt; &lt;li&gt;创建消息对象，指定主题、标签和消息体&lt;/li&gt; &lt;li&gt;发送消息&lt;/li&gt; &lt;li&gt;关闭生产者&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-java"&gt;//发送消息 public class RocketMQSendTest {     public static void main(String[] args) throws Exception {         //1. 创建消息生产者, 指定生产者所属的组名         DefaultMQProducer producer = new DefaultMQProducer(&amp;quot;myproducer-group&amp;quot;);          //2. 指定Nameserver地址         producer.setNamesrvAddr(&amp;quot;192.168.109.131:9876&amp;quot;);         //3. 启动生产者         producer.start();         //4. 创建消息对象，指定主题、标签和消息体         Message msg = new Message(&amp;quot;myTopic&amp;quot;, &amp;quot;myTag&amp;quot;, (&amp;quot;RocketMQ Message&amp;quot;).getBytes());         //5. 发送消息         SendResult sendResult = producer.send(msg, 10000);         System.out.println(sendResult);         //6. 关闭生产者         producer.shutdown();     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;接收消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;消息接收步骤:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;创建消息消费者, 指定消费者所属的组名&lt;/li&gt; &lt;li&gt;指定Nameserver地址&lt;/li&gt; &lt;li&gt;指定消费者订阅的主题和标签&lt;/li&gt; &lt;li&gt;设置回调函数，编写处理消息的方法&lt;/li&gt; &lt;li&gt;启动消息消费者&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-java"&gt;    public static void main(String[] args) throws MQClientException {         //1. 创建消息消费者, 指定消费者所属的组名         DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(                 &amp;quot;myconsumer- group&amp;quot;);         //2. 指定Nameserver地址          consumer.setNamesrvAddr(&amp;quot;192.168.109.131:9876&amp;quot;);         //3. 指定消费者订阅的主题和标签         consumer.subscribe(&amp;quot;myTopic&amp;quot;, &amp;quot;*&amp;quot;);         //4. 设置回调函数，编写处理消息的方法          consumer.registerMessageListener(new MessageListenerConcurrently() {             @Override             public ConsumeConcurrentlyStatus consumeMessage(                     List&amp;lt;MessageExt&amp;gt; msgs, ConsumeConcurrentlyContext context) {                 System.out.println(&amp;quot;Receive New Messages: &amp;quot; + msgs);                 //返回消费状态                 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;             }         });         //5. 启动消息消费者         consumer.start();         System.out.println(&amp;quot;Consumer Started.&amp;quot;);     } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;案例&lt;/h2&gt; &lt;p&gt;接下来我们模拟一种场景: 下单成功之后，向下单用户发送短信。设计图如下&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-15-11.33.44.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h3&gt;订单微服务发送消息&lt;/h3&gt; &lt;p&gt;1 在 shop-order 中添加rocketmq的依赖&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;            &amp;lt;!--rocketmq--&amp;gt;             &amp;lt;dependency&amp;gt;                 &amp;lt;groupId&amp;gt;org.apache.rocketmq&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;rocketmq-spring-boot-starter&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt;             &amp;lt;/dependency&amp;gt;             &amp;lt;dependency&amp;gt;                 &amp;lt;groupId&amp;gt;org.apache.rocketmq&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;rocketmq-client&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;4.4.0&amp;lt;/version&amp;gt;             &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 添加配置&lt;/p&gt; &lt;pre&gt;&lt;code class="language-properties"&gt;rocketmq: #rocketMQ服务的地址 name-server: 192.168.109.131:9876   producer:   # 生产者组   group: shop-order  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3 编写测试代码&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;@RestController @Slf4j public class OrderController2 {     @Autowired     private OrderService orderService;     @Autowired     private ProductService productService;     @Autowired     private RocketMQTemplate rocketMQTemplate;     //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单,这时候要调用商品微服务查询商品信息&amp;quot;);          //通过fegin调用商品微服务         Product product = productService.findByPid(pid);          if (product == null) {             Order order = new Order();             order.setPname(&amp;quot;下单失败&amp;quot;);             return order;         }         log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot; + JSON.toJSONString(product));          Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);          order.setPid(product.getPid());          order.setPname(product.getPname());          order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         //下单成功之后,将消息放到mq中          rocketMQTemplate.convertAndSend(&amp;quot;order-topic&amp;quot;, order);         return order;     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;用户微服务订阅消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;1 修改 shop-user 模块配置&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;parent&amp;gt;       &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;        &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;        &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;shop-user&amp;lt;/artifactId&amp;gt;    &amp;lt;dependencies&amp;gt;     &amp;lt;dependency&amp;gt;        &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;        &amp;lt;artifactId&amp;gt;shop-common&amp;lt;/artifactId&amp;gt;        &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/dependency&amp;gt;     &amp;lt;dependency&amp;gt;       &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;       &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos- discovery&amp;lt;/artifactId&amp;gt;     &amp;lt;/dependency&amp;gt;     &amp;lt;dependency&amp;gt;       &amp;lt;groupId&amp;gt;org.apache.rocketmq&amp;lt;/groupId&amp;gt;        &amp;lt;artifactId&amp;gt;rocketmq-spring-boot-starter&amp;lt;/artifactId&amp;gt;        &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt;     &amp;lt;/dependency&amp;gt;     &amp;lt;dependency&amp;gt;       &amp;lt;groupId&amp;gt;org.apache.rocketmq&amp;lt;/groupId&amp;gt;        &amp;lt;artifactId&amp;gt;rocketmq-client&amp;lt;/artifactId&amp;gt;        &amp;lt;version&amp;gt;4.4.0&amp;lt;/version&amp;gt;     &amp;lt;/dependency&amp;gt;   &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 修改主类&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;@SpringBootApplication @EnableDiscoveryClient public class UserApplication {   public static void main(String[] args) {    SpringApplication.run(UserApplication.class, args);   }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3 修改配置文件&lt;/p&gt; &lt;pre&gt;&lt;code class="language-yaml"&gt;server:   port: 8071 spring:   application:     name: service-user   datasource:     driver-class-name: com.mysql.jdbc.Driver     url: jdbc:mysql:///shop? serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: root   jpa:     properties:     hibernate:     hbm2ddl:     auto: update     dialect: org.hibernate.dialect.MySQL5InnoDBDialect cloud:   nacos:     discovery:       server-addr: 127.0.0.1:8848 rocketmq:       name-server: 192.168.109.131:9876   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4 编写消息接收服务&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.service; //发送短信的服务 @Slf4j @Service @RocketMQMessageListener(consumerGroup = &amp;quot;shop-user&amp;quot;, topic = &amp;quot;order-topic&amp;quot;)  public class SmsService implements RocketMQListener&amp;lt;Order&amp;gt; {   @Override   public void onMessage(Order order) {     log.info(&amp;quot;收到一个订单信息{},接下来发送短信&amp;quot;, JSON.toJSONString(order));   }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;5 启动服务，执行下单操作，观看后台输出&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;发送不同类型的消息&lt;/strong&gt;&lt;/h2&gt; &lt;h3&gt;&lt;strong&gt;普通消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;可靠同步发送&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;同步发送是指消息发送方发出数据后，会在收到接收方发回响应之后才发下一个数据包的通讯方式。此种方式应用场景非常广泛，例如重要通知邮件、报名短信通知、营销短信系统等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;可靠异步发送&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;异步发送是指发送方发出数据后，不等接收方发回响应，接着发送下个数据包的通讯方式。发送 方通过回调接口接收服务器响应，并对响应结果进行处理。&lt;/p&gt; &lt;p&gt;异步发送一般用于链路耗时较长，对 RT 响应时间较为敏感的业务场景，例如用户视频上传后通知 启动转码服务，转码完成后通知推送转码结果等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;单向发送&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;单向发送是指发送方只负责发送消息，不等待服务器回应且没有回调函数触发，即只发送请求不 等待应答。&lt;/p&gt; &lt;p&gt;适用于某些耗时非常短，但对可靠性要求并不高的场景，例如日志收集。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;!--依赖--&amp;gt; &amp;lt;dependency&amp;gt;    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-java"&gt;//测试 @RunWith(SpringRunner.class) @SpringBootTest(classes = OrderApplication.class) public class MessageTypeTest {     @Autowired     private RocketMQTemplate rocketMQTemplate; //同步消息      @Test     public void testSyncSend() {         //参数一: topic， 如果想添加tag 可以使用&amp;quot;topic:tag&amp;quot;的写法         // 参数二: 消息内容         SendResult sendResult = rocketMQTemplate.syncSend(&amp;quot;test-topic-1&amp;quot;, &amp;quot;这是一条同步消息&amp;quot;);         System.out.println(sendResult);     }      //异步消息     @Test     public void testSyncSendMsg () {         //参数一: topic, 如果想添加tag 可以使用&amp;quot;topic:tag&amp;quot;的写法         //参数二: 消息内容         //参数三: 回调函数, 处理返回结果         rocketMQTemplate.asyncSend(&amp;quot;test-topic-1&amp;quot;, &amp;quot;这是一条异步消息&amp;quot;, new                 SendCallback() {                     @Override                     public void onSuccess(SendResult sendResult) {                         System.out.println(sendResult);                     }                      @Override                     public void onException(Throwable throwable) {                         System.out.println(throwable);                     }                 }         );         //让线程不要终止         Thread.sleep(30000000);     }     //单向消息     @Test     public void testOneWay () {         rocketMQTemplate.sendOneWay(&amp;quot;test-topic-1&amp;quot;, &amp;quot;这是一条单向消息&amp;quot;);     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;三种发送方式的对比&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;&lt;strong&gt;发送方式&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;发送&lt;/strong&gt; &lt;strong&gt;TPS&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;发送结果反馈&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;可靠性&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;同步发送&lt;/td&gt;&lt;td&gt;快&lt;/td&gt;&lt;td&gt;有&lt;/td&gt;&lt;td&gt;不丢失&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;异步发送&lt;/td&gt;&lt;td&gt;快&lt;/td&gt;&lt;td&gt;有&lt;/td&gt;&lt;td&gt;不丢失&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;单向发送&lt;/td&gt;&lt;td&gt;最快&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;不丢失&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;&lt;strong&gt;顺序消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-16-12.18.04.png" alt="截屏2021-05-16 下午12.18.04" title="截屏2021-05-16 下午12.18.04" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//同步顺序消息[异步顺序 单向顺序写法类似]  public void testSyncSendOrderly() {   //第三个参数用于队列的选择   rocketMQTemplate.syncSendOrderly(&amp;quot;test-topic-1&amp;quot;, &amp;quot;这是一条异步顺序消息&amp;quot;, &amp;quot;xxxx&amp;quot;); } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;&lt;strong&gt;事务消息&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;RocketMQ提供了事务消息，通过事务消息就能达到分布式事务的最终一致。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;事务消息交互流程:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/16/2021-05-16-12.19.01.png" alt="截屏2021-05-16 下午12.19.01" title="截屏2021-05-16 下午12.19.01" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;两个概念:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;半事务消息:暂不能投递的消息，发送方已经成功地将消息发送到了RocketMQ服务端，但是服务端未 收到生产者对该消息的二次确认，此时该消息被标记成“暂不能投递”状态，处于该种状态下的消息即半 事务消息。&lt;/p&gt; &lt;p&gt;消息回查:由于网络闪断、生产者应用重启等原因，导致某条事务消息的二次确认丢失，RocketMQ服 务端通过扫描发现某条消息长期处于“半事务消息”时，需要主动向消息生产者询问该消息的最终状态 (Commit 或是 Rollback)，该询问过程即消息回查。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;事务消息发送步骤:&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;发送方将半事务消息发送至RocketMQ服务端。&lt;/li&gt; &lt;li&gt;RocketMQ服务端将消息持久化之后，向发送方返回Ack确认消息已经发送成功，此时消息为半事务消息。&lt;/li&gt; &lt;li&gt;发送方开始执行本地事务逻辑。&lt;/li&gt; &lt;li&gt;发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback)，服务端收到Commit 状态则将半事务消息标记为可投递，订阅方最终将收到该消息;服务端收到 Rollback 状 态则删除半事务消息，订阅方将不会接受该消息。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;事务消息回查步骤&lt;/strong&gt;:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;在断网或者是应用重启的特殊情况下，上述步骤4提交的二次确认最终未到达服务端，经过固定时 间后服务端将对该消息发起消息回查。&lt;/li&gt; &lt;li&gt;发送方收到消息回查后，需要检查对应消息的本地事务执行的最终结果。&lt;/li&gt; &lt;li&gt;发送方根据检查得到的本地事务的最终状态再次提交二次确认，服务端仍按照步骤4对半事务消息进行操作。&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code class="language-java"&gt;//事物日志 @Entity(name = &amp;quot;shop_txlog&amp;quot;) @Data public class TxLog {     @Id     private String txLogId;     private String content;     private Date date; }   @Service public class OrderServiceImpl4 {     @Autowiredprivate     OrderDao orderDao;     @Autowired     private TxLogDao txLogDao;     @Autowired     private RocketMQTemplate rocketMQTemplate;      public void createOrderBefore(Order order) {         String txId = UUID.randomUUID().toString();         //发送半事务消息         rocketMQTemplate.sendMessageInTransaction( &amp;quot;tx_producer_group&amp;quot;,&amp;quot;tx_topic&amp;quot;, MessageBuilder.withPayload(order).setHeader(&amp;quot;txId&amp;quot;, txId).build(),order);     }      //本地事物     @Transactional     public void createOrder(String txId, Order order) {         //本地事物代码         orderDao.save(order);         // 记录日志到数据库,回查使用         TxLog txLog = new TxLog();         txLog.setTxLogId(txId);         txLog.setContent(&amp;quot;事物测试&amp;quot;);         txLog.setDate(new Date());         txLogDao.save(txLog);     } }  @RocketMQTransactionListener(txProducerGroup = &amp;quot;tx_producer_group&amp;quot;) public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener {     @Autowired     private TxLogDao txLogDao;     @Autowired     private OrderServiceImpl4 orderServiceImpl4;      //执行本地事物     @Override     public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {         try { //本地事物             orderServiceImpl4.createOrder((String)             msg.getHeaders().get(&amp;quot;txId&amp;quot;), (Order) arg);             return RocketMQLocalTransactionState.COMMIT;         } catch (Exception e) {             return RocketMQLocalTransactionState.ROLLBACK;         }     }      //消息回查     @Override     public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { //查询日志记录         TxLog txLog = txLogDao.findById((String)                 msg.getHeaders().get(&amp;quot;txId&amp;quot;)).get();         if (txLog == null) {             return RocketMQLocalTransactionState.COMMIT;         } else {             return RocketMQLocalTransactionState.ROLLBACK;         }     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;&lt;strong&gt;消息消费要注意的细节&lt;/strong&gt;&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;@RocketMQMessageListener( consumerGroup = &amp;quot;shop&amp;quot;,//消费者分组 topic = &amp;quot;order-topic&amp;quot;,//要消费的主题 consumeMode = ConsumeMode.CONCURRENTLY, //消费模式:无序和有序  messageModel = MessageModel.CLUSTERING, //消息模式:广播和集群,默认是集群 ) public class SmsService implements RocketMQListener&amp;lt;Order&amp;gt; { } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;RocketMQ支持两种消息模式&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;广播消费: 每个消费者实例都会收到消息,也就是一条消息可以被每个消费者实例处理;&lt;/li&gt; &lt;li&gt;集群消费: 一条消息只能被一个消费者实例消费&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Sun, 16 May 2021 04:29:00 GMT</pubDate>
    </item>
    <item>
      <title>Sleuth--链路追踪</title>
      <link>https://maruifu.cn/article/190</link>
      <content:encoded>&lt;h2&gt;链路追踪介绍&lt;/h2&gt; &lt;p&gt;在大型系统的微服务化构建中，一个系统被拆分成了许多模块。这些模块负责不同的功能，组合成系 统，最终可以提供丰富的功能。在这种架构中，一次请求往往需要涉及到多个服务。互联网应用构建在 不同的软件模块集上，这些软件模块，有可能是由不同的团队开发、可能使用不同的编程语言来实现、 有可能布在了几千台服务器，横跨多个不同的数据中心，也就意味着这种架构形式也会存在一些问题:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;如何快速发现问题?&lt;/li&gt; &lt;li&gt;如何判断故障影响范围?&lt;/li&gt; &lt;li&gt;如何梳理服务依赖以及依赖的合理性?&lt;/li&gt; &lt;li&gt;如何分析链路性能问题以及实时容量规划?&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.36.34.png" alt="链路追踪介绍" title="链路追踪介绍" /&gt;&lt;/p&gt; &lt;p&gt;分布式链路追踪(Distributed Tracing)，就是将一次分布式请求还原成调用链路，进行日志记录，性 能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器 上、每个服务节点的请求状态等等。&lt;/p&gt; &lt;p&gt;常见的链路追踪技术有下面这些:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;cat&lt;/strong&gt; 由大众点评开源，基于Java开发的实时应用监控平台，包括实时应用监控，业务监控 。 集成 方案是通过代码埋点的方式来实现监控，比如: 拦截器，过滤器等。 对代码的侵入性很大，集成 成本较高。风险较大。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;zipkin&lt;/strong&gt; 由Twitter公司开源，开放源代码分布式的跟踪系统，用于收集服务的定时数据，以解决微 服务架构中的延迟问题，包括:数据的收集、存储、查找和展现。该产品结合spring-cloud-sleuth 使用较为简单， 集成很方便， 但是功能较简单。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;pinpoint&lt;/strong&gt; :Pinpoint是韩国人开源的基于字节码注入的调用链分析，以及应用监控分析工具。特点 是支持多种插件，UI功能强大，接入端无代码侵入。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;skywalking&lt;/strong&gt;:SkyWalking是本土开源的基于字节码注入的调用链分析，以及应用监控分析工具。 特点是支持多种插件，UI功能较强，接入端无代码侵入。目前已加入Apache孵化器。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Sleuth&lt;/strong&gt;:SpringCloud 提供的分布式系统中链路追踪解决方案。&lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;注意:SpringCloud alibaba&lt;/strong&gt;技术栈中并没有提供自己的链路追踪技术的，我们可以采用&lt;strong&gt;Sleuth +Zinkin&lt;/strong&gt;来做链路追踪解决方案&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;Sleuth入门&lt;/h2&gt; &lt;h3&gt;Sleuth介绍&lt;/h3&gt; &lt;p&gt;SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的 设计， 先来了解一下Sleuth中的术语和相关概念。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Trace&lt;/strong&gt; 由一组Trace Id相同的Span串联形成一个树状结构。为了实现请求跟踪，当请求到达分布式系统 的入口端点时，只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId)，同时在分布式系统 内部流转的时候，框架始终保持传递该唯一值，直到整个请求的返回。那么我们就可以使用该唯一标识 将所有的请求串联起来，形成一条完整的请求链路。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Span&lt;/strong&gt; 代表了一组基本的工作单元。为了统计各处理单元的延迟，当请求到达各个服务组件的时候，也 通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳， 就能统计该span的调用时间，除此之外，我们还可以获取如事件的名称。请求信息等元数据。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Annotation&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;用它记录一段时间内的事件，内部使用的重要注释:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;cs(Client Send)客户端发出请求，开始一个请求的生命&lt;/li&gt; &lt;li&gt;sr(Server Received)服务端接受到请求开始进行处理， sr-cs = 网络延迟(服务调用的时间)&lt;/li&gt; &lt;li&gt;ss(Server Send)服务端处理完毕准备发送到客户端，ss - sr = 服务器上的请求处理时间&lt;/li&gt; &lt;li&gt;cr(Client Reveived)客户端接受到服务端的响应，请求结束。 cr - sr = 请求的总时间&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.40.39.png" alt="Annotation" title="Annotation" /&gt;&lt;/p&gt; &lt;h3&gt;Sleuth入门&lt;/h3&gt; &lt;p&gt;微服务名称, traceId, spanid,是否将链路的追踪结果输出到第三方平台&lt;/p&gt; &lt;p&gt;[api-gateway,3977125f73391553,3977125f73391553,false]&lt;/p&gt; &lt;p&gt;[service-order,3977125f73391553,57547b5bf71f8242,false]&lt;/p&gt; &lt;p&gt;[service-product,3977125f73391553,449f5b3f3ef8d5c5,false]&lt;/p&gt; &lt;p&gt;接下来通过之前的项目案例整合Sleuth，完成入门案例的编写。 修改common父工程引入Sleuth依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!--链路追踪 Sleuth--&amp;gt;  &amp;lt;dependency&amp;gt;     &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;spring-cloud-starter-sleuth&amp;lt;/artifactId&amp;gt;   &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;启动微服务，调用之后，我们可以在控制台观察到sleuth的日志输出&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.43.07.png" alt="截屏2021-05-15 下午10.43.07" title="截屏2021-05-15 下午10.43.07" /&gt;&lt;/p&gt; &lt;p&gt;其中 5399d5cb061971bd 是TraceId， 5399d5cb061971bd 是SpanId，依次调用有一个全局的 TraceId，将调用链路串起来。仔细分析每个微服务的日志，不难看出请求的具体过程。&lt;/p&gt; &lt;p&gt;查看日志文件并不是一个很好的方法，当微服务越来越多日志文件也会越来越多，通过Zipkin可以将日 志聚合，并进行可视化展示和全文检索。&lt;/p&gt; &lt;h2&gt;Zipkin的集成&lt;/h2&gt; &lt;h3&gt;ZipKin介绍&lt;/h3&gt; &lt;p&gt;Zipkin 是 Twitter 的一个开源项目，它基于Google Dapper实现，它致力于收集服务的定时数据，以解 决微服务架构中的延迟问题，包括数据的&lt;strong&gt;收集、存储、查找和展现&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;我们可以使用它来收集各个服务器上请求链路的跟踪数据，并通过它提供的REST API接口来辅助我们查 询跟踪数据以实现对分布式系统的监控程序，从而及时地发现系统中出现的延迟升高问题并找出系统性 能瓶颈的根源。&lt;/p&gt; &lt;p&gt;除了面向开发的 API 接口之外，它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链 路明细，比如:可以查询某段时间内各用户请求的处理时间等。&lt;/p&gt; &lt;p&gt;Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.44.38.png" alt="截屏2021-05-15 下午10.44.38" title="截屏2021-05-15 下午10.44.38" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;上图展示了&lt;/strong&gt; &lt;strong&gt;Zipkin&lt;/strong&gt; &lt;strong&gt;的基础架构，它主要由&lt;/strong&gt; &lt;strong&gt;4&lt;/strong&gt; &lt;strong&gt;个核心组件构成:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Collector:收集器组件，它主要用于处理从外部系统发送过来的跟踪信息，将这些信息转换为 Zipkin内部处理的 Span 格式，以支持后续的存储、分析、展示等功能。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Storage:存储组件，它主要对处理收集器接收到的跟踪信息，默认会将这些信息存储在内存中， 我们也可以修改此存储策略，通过使用其他存储组件将跟踪信息存储到数据库中。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;RESTful API:API 组件，它主要用来提供外部访问接口。比如给客户端展示跟踪信息，或是外接 系统访问以实现监控等。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Web UI:UI 组件， 基于API组件实现的上层应用。通过UI组件用户可以方便而有直观地查询和分 析跟踪信息。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Zipkin分为两端，一个是 Zipkin服务端，一个是 Zipkin客户端，客户端也就是微服务的应用。 客户端会 配置服务端的 URL 地址，一旦发生服务间的调用的时候，会被配置在微服务里面的 Sleuth 的监听器监 听，并生成相应的 Trace 和 Span 信息发送给服务端。&lt;/p&gt; &lt;h3&gt;ZipKin服务端安装&lt;/h3&gt; &lt;p&gt;第1步: 下载ZipKin的jar包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;https://search.maven.org/remote_content?g=io.zipkin.java&amp;amp;a=zipkin-server&amp;amp;v=LATEST&amp;amp;c=exec &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 通过命令行，输入下面的命令启动ZipKin Server&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -jar zipkin-server-2.12.9-exec.jar &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步:通过浏览器访问 http://localhost:9411访问&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.47.12.png" alt="截屏2021-05-15 下午10.47.12" title="截屏2021-05-15 下午10.47.12" /&gt;&lt;/p&gt; &lt;h3&gt;Zipkin客户端集成&lt;/h3&gt; &lt;p&gt;ZipKin客户端和Sleuth的集成非常简单，只需要在微服务中添加其依赖和配置即可。&lt;/p&gt; &lt;p&gt;第1步:在每个微服务上添加依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;    &amp;lt;artifactId&amp;gt;spring-cloud-starter-zipkin&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步:添加配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:   zipkin:     #开启zipkin分析     enabled: true     #zipkin服务地址     baseUrl: http://127.0.0.1:9411/     #让nacos把它当成一个URL，而不要当做服务名     discoveryClientEnabled: false    sleuth:     sampler:       #限速器，每秒采集10个请求，防止大并发过载。推荐       #rate: 10       #采集率，大并发可能采集率数量也会很高。采样的百分比       probability: 0.1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步: 访问微服务&lt;/p&gt; &lt;pre&gt;&lt;code&gt;http://localhost:7000/order-serv/order/prod/1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第4步: 访问zipkin的UI界面，观察效果&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.51.23.png" alt="截屏2021-05-15 下午10.51.23" title="截屏2021-05-15 下午10.51.23" /&gt;&lt;/p&gt; &lt;p&gt;第5步:点击其中一条记录，可观察一次访问的详细线路。&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.51.43.png" alt="截屏2021-05-15 下午10.51.43" title="截屏2021-05-15 下午10.51.43" /&gt;&lt;/p&gt; &lt;h2&gt;ZipKin数据持久化&lt;/h2&gt; &lt;p&gt;Zipkin Server默认会将追踪数据信息保存到内存，但这种方式不适合生产环境。Zipkin支持将追踪数据持久化到mysql数据库或elasticsearch中。&lt;/p&gt; &lt;h3&gt;使用mysql实现数据持久化&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS zipkin_spans (   `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',   `trace_id` BIGINT NOT NULL,   `id` BIGINT NOT NULL,   `name` VARCHAR(255) NOT NULL,   `parent_id` BIGINT,   `debug` BIT(1),   `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;  ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate'; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations'; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds'; ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames'; ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';  CREATE TABLE IF NOT EXISTS zipkin_annotations (   `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this   means the trace uses 128 bit traceIds instead of 64 bit',   `trace_id` BIGINT NOT NULL COMMENT 'coincides with   zipkin_spans.trace_id',   `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',   `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',   `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller   than 64KB',   `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if   Annotation',   `a_timestamp` BIGINT COMMENT 'Used to implement TTL;   Annotation.timestamp or zipkin_spans.timestamp',   `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',   `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',   `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint   is null',   `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;  ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';  ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames'; ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';  ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';  ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';  CREATE TABLE IF NOT EXISTS zipkin_dependencies (   `day` DATE NOT NULL,   `parent` VARCHAR(255) NOT NULL,   `child` VARCHAR(255) NOT NULL,   `call_count` BIGINT ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`,`child`);   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 在启动ZipKin Server的时候,指定数据保存的mysql的信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql -- MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root - -MYSQL_PASS=root &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;使用elasticsearch实现数据持久化&lt;/h3&gt; &lt;p&gt;第1步: 下载elasticsearch 下载地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-8-4 第2步: 启动elasticsearch&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/15/2021-05-15-10.58.33.png" alt="截屏2021-05-15 下午10.58.33" title="截屏2021-05-15 下午10.58.33" /&gt;&lt;/p&gt; &lt;p&gt;第3步: 在启动ZipKin Server的时候，指定数据保存的elasticsearch的信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES- HOST=localhost:9200 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 15 May 2021 15:05:00 GMT</pubDate>
    </item>
    <item>
      <title>秒懂QPS、TPS、PV、UV、GMV、IP、RPS</title>
      <link>https://maruifu.cn/article/189</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;QPS、TPS、PV、UV、GMV、IP、RPS等各种名词，外行看起来很牛X，实际上每个程序员都是必懂知识点。下面我来一一解释一下。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;&lt;strong&gt;QPS&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Queries Per Second，每秒查询数。每秒能够响应的查询次数。&lt;/p&gt; &lt;p&gt;QPS 是一台服务器每秒能够相应的查询次数，即1秒内完成的请求数量，是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准&lt;/p&gt; &lt;p&gt;QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准，在因特网上，作为域名系统服务器的机器的性能经常用每秒查询率来衡量。每秒的响应请求数，也即是最大&lt;strong&gt;吞吐能力&lt;/strong&gt;。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;TPS&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Transactions Per Second 的缩写，每秒处理的事务数目。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时，收到服务器响应后结束计时，以此来计算使用的时间和完成的事务个数，最终利用这些信息作出的评估分。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;TPS 的过程包括：客户端请求服务端、服务端内部处理、服务端返回客户端。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;例如，访问一个 Index 页面会请求服务器 3 次，包括一次 html，一次 css，一次 js，那么访问这一个页面就会产生一个“T”，产生三个“Q”。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;QPS 与 TPS 区别&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;QPS基本类似于TPS，但是不同的是，对于一个Web页面的一次访问，形成一个TPS（就做一件事儿，打开Web网页）；但一次Web页面请求，可能产生多次对服务器的请求（html、css、js、images、files等），服务器对这些请求，就可计入QPS之中。每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。&lt;/p&gt; &lt;p&gt;一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时，收到服务器响应后结束计时，以此来计算使用的时间和完成的事务个数。&lt;/p&gt; &lt;p&gt;如果是对一个接口（单场景）压测，且这个接口内部不会再去请求其它接口，那么TPS等于QPS，否则，若这个接口内部还会再去请求其它接口（下载图片、文件等服务器接口），那么 TPS不等于QPS，通常是 QPS会大于TPS&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;PV&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;page view即页面浏览量&lt;/p&gt; &lt;p&gt;通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标。&lt;/p&gt; &lt;p&gt;用户每一次对网站中的每个页面访问均被记录 1 次。&lt;/p&gt; &lt;p&gt;用户对同一页面的多次刷新，访问量累计。&lt;/p&gt; &lt;p&gt;根据这个特性，刷网站的 PV 就很好刷了。&lt;/p&gt; &lt;p&gt;与 PV 相关的还有 &lt;strong&gt;RV&lt;/strong&gt;，即重复访问者数量（repeat visitors）。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;UV&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Unique Visitor 指独立访客访问数，统计1天内访问某站点的用户数(以 cookie 为依据)，一台电脑终端为一个访客。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;IP&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Internet Protocol独立 IP 数&lt;/p&gt; &lt;p&gt;是指 1 天内多少个独立的 IP 浏览了页面，即统计不同的 IP 浏览用户数量。&lt;/p&gt; &lt;p&gt;同一 IP 不管访问了几个页面，独立 IP 数均为 1；&lt;/p&gt; &lt;p&gt;不同的 IP 浏览页面，计数会加 1。&lt;/p&gt; &lt;p&gt;IP 是基于用户广域网 IP 地址来区分不同的访问者的，所以，多个用户（多个局域网 IP）在同一个路由器（同一个广域网 IP）内上网，可能被记录为一个独立 IP 访问者。如果用户不断更换 IP，则有可能被多次统计。&lt;/p&gt; &lt;h2&gt;RT&lt;/h2&gt; &lt;p&gt;Response Time 响应时间是一个系统最重要的指标之一，它的数值大小直接反应了系统的快慢。&lt;/p&gt; &lt;p&gt;响应时间是指系统对请求作出响应的时间。直观上看，这个指标与人对软件性能的主观感受是非常一致的，因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能，而不同功能的处理逻辑也千差万别，因而不同功能的响应时间也不尽相同，甚至同一功能在不同输入数据的情况下响应时间也不相同。所以，在讨论一个系统的响应时间时，人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然，往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。&lt;/p&gt; &lt;p&gt;对于单机的没有并发操作的应用系统而言，人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是，响应时间的绝对值并不能直接反映软件的性能的高低，软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说，响应时间小于100毫秒应该是不错的，响应时间在1秒左右可能属于勉强可以接受，如果响应时间达到3秒就完全难以接受了。而对于编译系统来说，完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间，但这些响应时间对于用户来说都是可以接受的。&lt;/p&gt; &lt;p&gt;响应时间是指执行一个请求从开始到最后收到响应数据所花费的总体时间,即从客户端发起请求到收到服务器响应结果的时间&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;GMV&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;Gross Merchandise Volume 的简称。只要是订单，不管消费者是否付款、卖家是否发货、是否退货，都可放进 GMV 。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;RPS&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;RPS&lt;/strong&gt; 代表吞吐率，即 Requests Per Second 的缩写。吞吐率是服务器并发处理能力的量化描述，单位是 reqs/s，指的是某个并发用户数下单位时间内处理的请求数。&lt;/p&gt; &lt;p&gt;某个并发用户数下单位时间内能处理的最大的请求数，称之为最大吞吐率。&lt;/p&gt; &lt;p&gt;有人把 RPS 说等效于 QPS。其实可以看作同一个统计方式，只是叫法不同而已。RPS/QPS，可以使用 apche ab 工具进行测量。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;QPS、PV 、RT 之间的关系&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;在进行系统性能压测和系统性能优化的时候，会涉及到QPS,PV,RT相关的概念， QPS,PV,RT之间的关系&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;对于大部分web系统，响应时间一般由CPU执行时间，线程等待时间（IO等待，sleep, wait）时间组成，QPS和RT成反比关系&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在实际的测试环境中，QPS和RT并不是非常直接的反比关系&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;QPS 是什么&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;QPS：单个进程每秒请求服务器的成功次数 QPS = req/sec = 请求数/秒&lt;/p&gt; &lt;p&gt;&lt;strong&gt;QPS如何统计&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;QPS统计方式 [一般使用 http_load 进行统计] QPS = 总请求数 / ( 进程总数 * 请求时间 )&lt;/p&gt; &lt;p&gt;&lt;strong&gt;根据QPS推算PV：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;单台服务器每天PV计算:&lt;/p&gt; &lt;p&gt;公式1：每天总PV = QPS * 3600 * 6 公式2：每天总PV = QPS * 3600 * 8&lt;/p&gt; &lt;p&gt;&lt;strong&gt;根据QPS，PV推算服务器数量&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;服务器数量 = 每天总PV / 单台服务器每天总PV&lt;/p&gt; &lt;p&gt;峰值QPS和机器计算公式：&lt;/p&gt; &lt;p&gt;原理：每天80%的访问集中在20%的时间里，这20%时间叫做峰值时间&lt;/p&gt; &lt;p&gt;峰值时间每秒请求数(QPS)：( 总PV数 * 80% ) / ( 每天秒数 * 20% )&lt;/p&gt; &lt;p&gt;峰值机器数量：峰值时间QPS / 单台机器的QPS&lt;/p&gt; &lt;p&gt;例子：&lt;/p&gt; &lt;p&gt;问：每天300w PV 的在单台机器上，这台机器需要多少QPS？&lt;/p&gt; &lt;p&gt;答：( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)&lt;/p&gt; &lt;p&gt;问：如果一台机器的QPS是58，需要几台机器来支持？&lt;/p&gt; &lt;p&gt;答：139 / 58 = 3&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;对于大部分web系统，响应时间一般由CPU执行时间，线程等待时间（IO等待，sleep, wait）时间组成，QPS和RT成反比关系&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在实际的测试环境中，QPS和RT并不是非常直接的反比关系&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;最佳线程数&lt;/h2&gt; &lt;p&gt;性能压测的情况下，起初随着用户数的增加，QPS会上升，当到了一定的阀值之后，用户数量增加QPS并不会增加，或者增加不明显，同时请求的响应时间却大幅增加，这个阀值我们认为是最佳线程数。&lt;/p&gt; &lt;p&gt;刚好消耗完服务器的瓶颈资源的临界线程数，公式如下&lt;/p&gt; &lt;p&gt;最佳线程数量 =（（线程等待时间 + 线程CPU执行时间）/ 线程CPU执行时间）* CPU数量&lt;/p&gt; &lt;p&gt;特性：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;在达到最佳线程数的时候，线程数量继续递增，则QPS不变，而响应时间变长，持续递增线程数量，则QPS开始下降&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;每个系统都有其最佳线程数量，但是不同状态下，最佳线程数量是会变化的&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;瓶颈资源可以是CPU,可以是内存，可以是锁资源，IO资源&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;超过最佳线程数，会导致资源的竞争；超过最佳线程数，会响应时间递增。&lt;/p&gt; &lt;h3&gt;为什么要找最佳线程数&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;过多的线程只会造成，更多的内存开销，更多的CPU开销，但是对提升QPS确毫无帮助&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;找到最佳线程数后通过简单的设置，可以让web系统更加稳定，得到最高，最稳定的QPS输出&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;最佳线程数的获取：&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;通过用户慢慢递增来进行性能压测，观察QPS，响应时间&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;根据公式计算:服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;单用户压测，查看CPU的消耗，然后直接乘以百分比，再进行压测，一般这个值的附近应该就是最佳线程数量。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;影响最佳线程数的主要因素&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;IO&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;IO开销较多的应用其CPU线程等待时间会比较长，所以线程数量可以开的多一些，相反则线程数量要少一些，其实有两种极端，纯IO的应用，比如proxy，则线程数量可以开到非常大（实在太大了则需要考虑线程切换的开销），这种应用基本上后端（比如这个proxy是代理搜索的）的QPS能有多少，proxy就有多少。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;CPU&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对于耗CPU的计算，这种情况一般来讲只能开到CPU个数的线程数量。但是并不是说这种应用的QPS就不高，往往这种应用的QPS可以很高，因为耗CPU计算的应用，往往处理单次请求的时间会很短。&lt;/p&gt; &lt;h3&gt;QPS和线程数的关系&lt;/h3&gt; &lt;p&gt;在最佳线程数量之前，QPS和线程是互相递增的关系，线程数量到了最佳线程之后，QPS持平，不在上升，甚至略有下降，同时响应时间持续上升。&lt;/p&gt; &lt;p&gt;同一个系统而言，最佳线程数越多，QPS越高&lt;/p&gt; &lt;h2&gt;并发数（Parallels）&lt;/h2&gt; &lt;p&gt;并发数是指系统同时能处理的请求数量，一般跟CPU个数，线程数有关，这反应了系统的负载能力&lt;/p&gt; &lt;p&gt;并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量相比，并发用户数是一个更直观但也更笼统的性能指标。实际上，并发用户数是一个非常不准确的指标，因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。一网站系统为例，假设用户只有注册后才能使用，但注册用户并不是每时每刻都在使用该网站，因此具体一个时刻只有部分注册用户同时在线，在线用户就在浏览网站时会花很多时间阅读网站上的信息，因而具体一个时刻只有部分在线用户同时向系统发出请求。这样，对于网站系统我们会有三个关于用户数的统计数字：注册用户数、在线用户数和同时发请求用户数。由于注册用户可能长时间不登陆网站，使用注册用户数作为性能指标会造成很大的误差。而在线用户数和同事发请求用户数都可以作为性能指标。相比而言，以在线用户作为性能指标更直观些，而以同时发请求用户数作为性能指标更准确些。&lt;/p&gt; &lt;h2&gt;吞吐量（Throughput）&lt;/h2&gt; &lt;p&gt;吞吐量是指单位时间内系统能处理的请求数量，体现系统处理请求的能力，这是目前最常用的性能测试指标&lt;/p&gt; &lt;p&gt;吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言，吞吐量与响应时间成严格的反比关系，实际上此时吞吐量就是响应时间的倒数。前面已经说过，对于单用户的系统，响应时间（或者系统响应时间和应用延迟时间）可以很好地度量系统的性能，但对于并发系统，通常需要用吞吐量作为性能指标。&lt;/p&gt; &lt;p&gt;对于一个多用户的系统，如果只有一个用户使用时系统的平均响应时间是t，当有你n个用户使用时，每个用户看到的响应时间通常并不是n×t，而往往比n×t小很多（当然，在某些特殊情况下也可能比n×t大，甚至大很多）。这是因为处理每个请求需要用到很多资源，由于每个请求的处理过程中有许多不走难以并发执行，这导致在具体的一个时间点，所占资源往往并不多。也就是说在处理单个请求时，在每个时间点都可能有许多资源被闲置，当处理多个请求时，如果资源配置合理，每个用户看到的平均响应时间并不随用户数的增加而线性增加。实际上，不同系统的平均响应时间随用户数增加而增长的速度也不大相同，这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言，吞吐量是一个比较通用的指标，两个具有不同用户数和用户使用模式的系统，如果其最大吞吐量基本一致，则可以判断两个系统的处理能力基本一致。&lt;/p&gt; &lt;h2&gt;并发量、QPS、RT 之间的关系：&lt;/h2&gt; &lt;p&gt;QPS = 并发量 / 平均响应时间    （推荐）&lt;/p&gt; &lt;p&gt;并发量 = QPS * 平均响应时间&lt;/p&gt; &lt;p&gt;假设每天80%的访问集中在20%的时间里，这20%时间叫做峰值时间&lt;/p&gt; &lt;p&gt;公式：( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)&lt;/p&gt; &lt;p&gt;机器：峰值时间每秒QPS / 单台机器的QPS = 需要的机器&lt;/p&gt; &lt;p&gt;每天300w PV 的在单台机器上，这台机器需要多少QPS？&lt;/p&gt; &lt;p&gt;( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)&lt;/p&gt; &lt;p&gt;如果一台机器的QPS是58，需要几台机器来支持？&lt;/p&gt; &lt;p&gt;139 / 58 = 3  （进一法，取上限）&lt;/p&gt; &lt;p&gt;单线程QPS公式，QPS=1000ms/RT,对同一个系统而言，支持的线程数越多，QPS越高。&lt;/p&gt; &lt;p&gt;假设一个RT是80ms,则可以很容易的计算出QPS,QPS = 1000/80 = 12.5&lt;/p&gt; &lt;p&gt;多线程场景，如果把服务端的线程数提升到2，那么整个系统的QPS则为 2*（1000/80） = 25,&lt;/p&gt; &lt;p&gt;可见QPS随着线程的增加而线性增长，那QPS上不去就加线程呗，听起来很有道理，公司也说的通，但是往往现实并非如此。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;如何提升RT（响应时间）&lt;/strong&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;减少 IO 的响应时间，减少 IO 的调用次数&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;并发HTTP请求，无上下文依赖，多个连接，一个线程&lt;/p&gt; &lt;p&gt;HTTP连接池（长连接，keep-alive）&lt;/p&gt; &lt;ul&gt; &lt;li&gt;减少 CPU 的使用时间&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;forest 循环的例子&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;如何提升 QPS（每秒查询数）&lt;/strong&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;减少 CPU 的使用时间&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;增加 CPU 的数量&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;减少同步锁&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;如果 CPU 不能被压到 85% 以上，并且此时的OPS已经达到了峰值，则说明另有瓶颈，接下去要重点关注内存&lt;/p&gt; &lt;p&gt;&lt;strong&gt;排查内存是否有瓶颈&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;判断依据，是在最佳线程数量 * 5 左右的情况下，进行压测，观察 Old 区内存增长是否正常&lt;/p&gt; &lt;p&gt;性能压测要关注使用了多少用户数，目前的压测方式容易遗漏内存瓶颈&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Proxy 应用（耗IO）的线程越多越好，当线程达到过多时，线程本身资源的开销也会成为瓶颈，线程本身也是一个资源。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;所以这类Proxy应用一般采取轻程模型，NIO解决，如 nginx&lt;/p&gt; &lt;ul&gt; &lt;li&gt;计算型应用（耗CPU），线程数量就是CPU的核数，如搜索索引服务器，需要做大量的计算排序，非常耗CPU资源&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Sun, 28 Mar 2021 08:10:00 GMT</pubDate>
    </item>
    <item>
      <title>后端开发术语</title>
      <link>https://maruifu.cn/article/188</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;后台开发作为互联网技术领域的掌上明珠，一直都是开发者们的追逐的高峰。本文将从后台开发所涉及到的技术术语出发，基于系统开发、架构设计、网络通信等几个方面让大家对后台来发有一个清晰的了解，讲解全面易懂。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;系统开发&lt;/h2&gt; &lt;h4&gt;高内聚/低耦合&lt;/h4&gt; &lt;p&gt;高内聚指一个软件模块是由相关性很强的代码组成，只负责一项任务，也就是常说的单一责任原则。模块的内聚反映模块内部联系的紧密程度。&lt;/p&gt; &lt;p&gt;模块之间联系越紧密，其耦合性就越强，模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。一个完整的系统，模块与模块之间，尽可能的使其独立存在。**通常程序结构中各模块的内聚程度越高，模块间的耦合程度就越低。&lt;/p&gt; &lt;h4&gt;过度设计&lt;/h4&gt; &lt;p&gt;过度设计就是进行了过多的面向未来的设计或者说把相对简单的事情想复杂了，过度追求模块化、可扩展性、设计模式等，为系统增加了不必要的复杂度。&lt;/p&gt; &lt;h4&gt;过早优化&lt;/h4&gt; &lt;p&gt;过早指的不是在开发过程的早期，而是在还没弄清楚需求未来的变化的走向的时候。你的优化不仅可能导致你无法很好地实现新的需求，而且你对优化的预期的猜测有可能还是错的，导致实际上你除了把代码变复杂以外什么都没得到。&lt;/p&gt; &lt;p&gt;正确的方法是，&lt;strong&gt;先有质量地实现你的需求，写够testcase，然后做profile去找到性能的瓶颈，这个时候才做优化。&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;重构 (Refactoring)&lt;/h4&gt; &lt;p&gt;重构（Refactoring）就是通过调整程序代码改善软件的质量、性能，使其程序的设计模式和架构更趋合理，提高软件的扩展性和维护性。&lt;/p&gt; &lt;h4&gt;破窗效应&lt;/h4&gt; &lt;p&gt;又称破窗理论，破窗效应（Broken windows theory）是犯罪学的一个理论。此理论认为环境中的不良现象如果被放任存在，会诱使人们仿效，甚至变本加厉。一幢有少许破窗的建筑为例，如果那些窗不被修理好，可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内，如果发现无人居住，也许就在那里定居或者纵火。&lt;/p&gt; &lt;p&gt;应用在软件工程上就是，&lt;strong&gt;一定不能让系统代码或者架构设计的隐患有冒头的机会，否则随着时间的推移，隐患会越来越重&lt;/strong&gt;。反之，一个本身优质的系统，会让人不由自主的写出优质的代码。&lt;/p&gt; &lt;h4&gt;互不信任原则&lt;/h4&gt; &lt;p&gt;指在程序运行上下游的整个链路中，每个点都是不能保证绝对可靠的，任何一个点都可能随时发生故障或者不可预知的行为，包括机器网络、服务本身、依赖环境、输入和请求等，因此要处处设防。&lt;/p&gt; &lt;h4&gt;持久化 (Persistence)&lt;/h4&gt; &lt;p&gt;持久化是将程序数据在临时状态和持久状态间转换的机制。通俗的讲，就是临时数据（比如内存中的数据，是不能永久保存的）持久化为持久数据（比如持久化至数据库或者本地磁盘中，能够长久保存）。&lt;/p&gt; &lt;h4&gt;临界区&lt;/h4&gt; &lt;p&gt;临界区用来表示一种公共资源或者说是共享数据，可以被多个线程使用，但是每一次，只能有一个线程使用它，一旦临界区资源被占用，其他线程要想使用这个资源，就必须等待。&lt;/p&gt; &lt;h4&gt;阻塞/非阻塞&lt;/h4&gt; &lt;p&gt;阻塞和非阻塞通常形容多线程间的相互影响。比如一个线程占用了临界区资源，那么其它所有需要这个资源的线程就必须在这个临界区中进行等待，等待会导致线程挂起。这种情况就是阻塞。此时，如果占用资源的线程一直不愿意释放资源，那么其它所有阻塞在这个临界区上的线程都不能工作。而非阻塞允许多个线程同时进入临界区。&lt;/p&gt; &lt;h4&gt;同步/异步&lt;/h4&gt; &lt;p&gt;通常同步和异步是指函数/方法调用方面。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;同步&lt;/strong&gt;就是在发出一个函数调用时，在没有得到结果之前，该调用就不返回。&lt;strong&gt;异步&lt;/strong&gt;调用会瞬间返回，但是异步调用瞬间返回并不代表你的任务就完成了，他会在后台起个线程继续进行任务，等任务执行完毕后通过回调callback或其他方式通知调用方。&lt;/p&gt; &lt;h4&gt;并发/并行&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;**并行(parallel)**指在同一时刻，有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看，二者都是一起执行的。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**并发(concurrency)**指在同一时刻只能有一条指令执行，但多个进程指令被快速的轮换执行，使得在宏观上具有多个进程同时执行的效果，但在微观上并不是同时执行的，只是把时间分成若干段，使多个进程快速交替的执行。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;架构设计&lt;/h2&gt; &lt;h4&gt;高并发 (High Concurrency)&lt;/h4&gt; &lt;p&gt;由于分布式系统的问世，高并发（High Concurrency）通常是指通过设计保证系统能够同时并行处理很多请求。通俗来讲，高并发是指在同一个时间点，有很多用户同时的访问同一 API 接口或者 Url 地址。它经常会发生在有大活跃用户量，用户高聚集的业务场景中。&lt;/p&gt; &lt;h4&gt;高可用 (High Availability)&lt;/h4&gt; &lt;p&gt;高可用HA（High Availability）是分布式系统架构设计中必须考虑的因素之一，它通常是指，一个系统经过专门的设计，以减少停工时间，而保持其服务的高度可用性。&lt;/p&gt; &lt;h4&gt;读写分离&lt;/h4&gt; &lt;p&gt;为了确保数据库产品的稳定性，很多数据库拥有双机热备功能。也就是，第一台数据库服务器，是对外提供增删改业务的生产服务器；第二台数据库服务器，主要进行读的操作。&lt;/p&gt; &lt;h4&gt;冷备/热备&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;冷备：两个服务器，一台运行，一台不运行做为备份&lt;/strong&gt;。这样一旦运行的服务器宕机，就把备份的服务器运行起来。冷备的方案比较容易实现，但冷备的缺点是主机出现故障时备机不会自动接管，需要主动切换服务。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;热备：即是通常所说的active/standby方式&lt;/strong&gt;，服务器数据包括数据库数据同时往两台或多台服务器写。当active服务器出现故障的时候，通过软件诊测（一般是通过心跳诊断）将standby机器激活，保证应用在短时间内完全恢复正常使用。当一台服务器宕机后，自动切换到另一台备用机使用。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;异地多活&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;异地多活一般是指在不同城市建立独立的数据中心&lt;/strong&gt;，“活”是相对于冷备份而言的，冷备份是备份全量数据，平时不支撑业务需求，只有在主机房出现故障的时候才会切换到备用机房，而多活，是指这些机房在日常的业务中也需要走流量，做业务支撑。&lt;/p&gt; &lt;h4&gt;负载均衡 (Load Balance)&lt;/h4&gt; &lt;p&gt;负载均衡，是对多台服务器进行流量分发的负载均衡服务。可在多个实例间自动分配应用程序的对外服务能力，通过消除单点故障提升应用系统的可用性，让您实现更高水平的应用程序容错能力，从而无缝提供分配应用程序流量所需的负载均衡容量，为您提供高效、稳定、安全的服务。&lt;/p&gt; &lt;h4&gt;动静分离&lt;/h4&gt; &lt;p&gt;动静分离是指在web服务器架构中，将静态页面与动态页面或者静态内容接口和动态内容接口分开不同系统访问的架构设计方法，进而提升整个服务访问性能和可维护性。&lt;/p&gt; &lt;h4&gt;集群&lt;/h4&gt; &lt;p&gt;单台服务器的并发承载能力总是有限的，当单台服务器处理能力达到性能瓶颈的时，将多台服务器组合起来提供服务，这种组合方式称之为集群，集群中每台服务器就叫做这个集群的一个“节点”，每个节点都能提供相同的服务，从而成倍的提升整个系统的并发处理能力。&lt;/p&gt; &lt;h4&gt;分布式&lt;/h4&gt; &lt;p&gt;分布式系统就是将一个完整的系统按照业务功能拆分成很多独立的子系统，每个子系统就被称为“服务”，分布式系统将请求分拣和分发到不同的子系统，让不同的服务来处理不同的请求。在分布式系统中，子系统独立运行，它们之间通过网络通信连接起来实现数据互通和组合服务。&lt;/p&gt; &lt;h4&gt;CAP理论&lt;/h4&gt; &lt;p&gt;CAP理论，指的是在一个分布式系统中，Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)，不能同时成立。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;**一致性：**它要求在同一时刻点，分布式系统中的所有数据备份都相同或者都处于同一状态。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**可用性：**在系统集群的一部分节点宕机后，系统依然能够正确的响应用户的请求。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;**分区容错性：**系统能够容忍节点之间的网络通信的故障。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;简单的来说，**在一个分布式系统中，最多能支持上面的两种属性。**但显然既然是分布式注定我们是必然要进行分区，既然分区，我们就无法百分百避免分区的错误。因此，我们只能在一致性和可用性去作出选择。&lt;/p&gt; &lt;p&gt;在分布式系统中，我们往往追求的是可用性，它的重要性比一致性要高，那么如何实现高可用，这里又有一个理论，就是 BASE 理论，它给 CAP 理论做了进一步的扩充。&lt;/p&gt; &lt;h4&gt;BASE理论&lt;/h4&gt; &lt;p&gt;BASE 理论指出：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Basically Available（基本可用）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Soft state（软状态）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Eventually consistent（最终一致性）&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果，理论的核心思想就是：&lt;strong&gt;我们无法做到强一致，但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性。&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;水平扩展/垂直扩展&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;水平扩展 Scale Out&lt;/strong&gt;通过增加更多的服务器或者程序实例来分散负载，从而提升存储能力和计算能力。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;垂直扩展 Scale Up&lt;/strong&gt; 提升单机处理能力。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;垂直扩展的方式又有两种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;（1）&lt;strong&gt;增强单机硬件性能&lt;/strong&gt;，例如：增加CPU核数如32核，升级更好的网卡如万兆，升级更好的硬盘如SSD，扩充硬盘容量如2T，扩充系统内存如128G;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;（2）&lt;strong&gt;提升单机软件或者架构性能&lt;/strong&gt;，例如：使用Cache来减少IO次数，使用异步来增加单服务吞吐量，使用无锁数据结构来减少响应时间；&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;平行扩容&lt;/h4&gt; &lt;p&gt;与水平扩展类似。集群服务器中的节点均为平行对等节点，当需要扩容时，可以通过添加更多节点以提高集群的服务能力。一般来说服务器中关键路径（如服务器中的登录、支付、核心业务逻辑等）都需要支持运行时动态平行扩容。&lt;/p&gt; &lt;h4&gt;弹性扩容&lt;/h4&gt; &lt;p&gt;指对部署的集群进行动态在线扩容。弹性扩容系统可以根据实际业务环境按照一定策略自动地添加更多的节点（包括存储节点、计算节点、网络节点）来增加系统容量、提高系统性能或者增强系统可靠性，或者同时完成这三个目标。&lt;/p&gt; &lt;h4&gt;状态同步/帧同步&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**状态同步：**状态同步是指服务器负责计算全部的游戏逻辑，并且广播这些计算的结果，客户端仅仅负责发送玩家的操作，以及表现收到的游戏结果。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;**特征：**状态同步安全性高，逻辑更新方便，断线重连快，但是开发效率较低，网络流量随游戏复杂度增加，服务器需要承载更大压力。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;**帧同步：**服务端只转发消息，不做任何逻辑处理，各客户端每秒帧数一致，在每一帧都处理同样的输入数据。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;**特征：**帧同步需要保证系统在相同的输入下，要有相同的输出。帧同步开发效率高，流量消耗低而且稳定，对服务器的压力非常小。但是网络要求高，断线重连时间长，客户端计算压力大。&lt;/p&gt; &lt;h2&gt;网络通信&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;连接池&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;预先建立一个连接缓冲池，并提供一套连接使用、分配、管理策略，使得该连接池中的连接可以得到高效、安全的复用，避免了连接频繁建立、关闭的开销。&lt;/p&gt; &lt;h4&gt;断线重连&lt;/h4&gt; &lt;p&gt;由于网络波动造成用户间歇性的断开与服务器的连接，待网络恢复之后服务器尝试将用户连接到上次断开时的状态和数据。&lt;/p&gt; &lt;h4&gt;会话保持&lt;/h4&gt; &lt;p&gt;会话保持是指在负载均衡器上的一种机制，可以识别客户端与服务器之间交互过程的关连性，在作负载均衡的同时还保证一系列相关连的访问请求都会分配到一台机器上。用人话来表述就是：在一次会话过程中发起的多个请求都会落到同一台机器上。&lt;/p&gt; &lt;h4&gt;长连接/短连接&lt;/h4&gt; &lt;p&gt;通常是指TCP的长连接和短连接。&lt;strong&gt;长连接&lt;/strong&gt;就是建立TCP连接后，一直保持这个连接，一般会中间彼此发送心跳来确认对应的存在，中间会做多次业务数据传输，一般不会主动断开连接。&lt;strong&gt;短连接&lt;/strong&gt;一般指建立连接后，执行一次事务后（如：http请求），然后就关掉这个连接。&lt;/p&gt; &lt;h4&gt;流量控制/拥塞控制&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;流量控制&lt;/strong&gt;防止发送方发的太快，耗尽接收方的资源，从而使接收方来不及处理。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;拥塞控制&lt;/strong&gt;防止发送方发的太快，使得网络来不及处理产生拥塞，进而引起这部分乃至整个网络性能下降的现象，严重时甚至会导致网络通信业务陷入停顿。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;惊群效应&lt;/h4&gt; &lt;p&gt;惊群效应也有人叫做雷鸣群体效应，不过叫什么，简言之，惊群现象就是多进程（多线程）在同时阻塞等待同一个事件的时候（休眠状态），如果等待的这个事件发生，那么他就会唤醒等待的所有进程（或者线程），但是最终却只可能有一个进程（线程）获得这个时间的“控制权”，对该事件进行处理，而其他进程（线程）获取“控制权”失败，只能重新进入休眠状态，这种现象和性能浪费就叫做惊群。&lt;/p&gt; &lt;h4&gt;NAT&lt;/h4&gt; &lt;p&gt;NAT（Network Address Translation，网络地址转换），就是替换IP报文头部的地址信息。NAT通常部署在一个组织的网络出口位置，通过将内部网络IP地址替换为出口的IP地址提供公网可达性和上层协议的连接能力。&lt;/p&gt; &lt;h2&gt;故障异常&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;宕机&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;宕机，一般情况下指的就是计算机主机出现意外故障而死机。其次，一些服务器例如数据库死锁也可以称为宕机，一些服务器的某些服务挂掉了，就可以这么说。&lt;/p&gt; &lt;h4&gt;coredump&lt;/h4&gt; &lt;p&gt;当程序出错而异常中断时，OS会把程序工作的当前状态存储成一个coredunmp文件。通常情况下coredump文件包含了程序运行时的内存，寄存器状态，堆栈指针，内存管理信息等。&lt;/p&gt; &lt;h4&gt;缓存穿透/击穿/雪崩&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**缓存穿透：**缓存穿透是指查询一个一定不存在的数据，由于缓存是不命中时需要从数据库查询，查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到数据库去查询，进而给数据库带来压力。&lt;/li&gt; &lt;li&gt;**缓存击穿：**缓存击穿是指热点key在某个时间点过期的时候，而恰好在这个时间点对这个Key有大量的并发请求过来，从而大量的请求打到db。&lt;/li&gt; &lt;li&gt;**缓存雪崩：**缓存雪崩是指缓存中数据大批量到过期时间，而查询数据量巨大，引起数据库压力过大甚至down机。&lt;/li&gt; &lt;li&gt;**与缓存击穿不同的是：**存击穿是热点key失效，缓存雪崩是大量的key同时失效。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;500/501/502/503/504/505&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**500 Internal Server Error：**内部服务错误，一般是服务器遇到意外情况，而无法完成请求。可能原因: 1、程序错误，例如：ASP或者PHP语法错误；2、高并发导致，系统资源限制不能打开过多的文件所致。&lt;/li&gt; &lt;li&gt;**501Not implemented：**服务器不理解或不支持请求的HTTP请求。&lt;/li&gt; &lt;li&gt;**502Bad Gateway：**WEB服务器故障，可能是由于程序进程不够，请求的php-fpm已经执行，但是由于某种原因而没有执行完毕，最终导致php-fpm进程终止。可能原因：1、Nginx服务器，php-cgi进程数不够用；2、PHP执行时间过长；3、php-cgi进程死掉；&lt;/li&gt; &lt;li&gt;**503Service Unavailable：**服务器目前无法使用。系统维护服务器暂时的无法处理客户端的请求，这只是暂时状态。可以联系下服务器提供商。&lt;/li&gt; &lt;li&gt;**504Gateway Timeout：**服务器504错误表示超时，是指客户端所发出的请求没有到达网关，请求没有到可以执行的php-fpm，一般是与nginx.conf的配置有关。&lt;/li&gt; &lt;li&gt;**505HTTP Version Not Supported：**服务器不支持请求中所用的 HTTP 协议版本。（HTTP 版本不受支持）&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;除了500错误可能是程序语言错误，其余的报错，都大概可以理解为服务器或者服务器配置出现问题。&lt;/p&gt; &lt;h4&gt;内存溢出/内存泄漏&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**内存溢出：**内存溢出（Out Of Memory）指程序申请内存时，没有足够的内存供申请者使用，或者说，给了你一块存储int类型数据的存储空间，但是你却存储long类型的数据，那么结果就是内存不够用，此时就会报错OOM,即所谓的内存溢出。&lt;/li&gt; &lt;li&gt;**内存泄漏：**内存泄漏（Memory Leak）指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;句柄泄漏&lt;/h4&gt; &lt;p&gt;句柄泄漏是进程在调用系统文件之后，没有释放已经打开的文件句柄。一般句柄泄漏后的现象是，机器变慢，CPU飙升，出现句柄泄漏的cgi或server的CPU使用率增加。&lt;/p&gt; &lt;h4&gt;死锁&lt;/h4&gt; &lt;p&gt;死锁是指两个或两个以上的线程在执行过程中，由于竞争资源或者由于彼此通信而造成的一种阻塞的现象，若无外力作用，它们都抑制处于阻塞状态并无法进行下去，此时称系统处于死锁状态或系统产生了死锁。&lt;/p&gt; &lt;h4&gt;软中断/硬中断&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**硬中断：**我们通常所说的中断指的是硬中断(hardirq)。由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。&lt;/li&gt; &lt;li&gt;**软中断：**1、通常是硬中断服务程序对内核的中断；2、为了满足实时系统的要求，中断处理应该是越快越好。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;linux为了实现这个特点，当中断发生的时候，硬中断处理那些短时间就可以完成的工作，而将那些处理事件比较长的工作，放到中断之后来完成，也就是软中断(softirq)来完成。&lt;/p&gt; &lt;h4&gt;毛刺&lt;/h4&gt; &lt;p&gt;在短暂的某一刻，服务器性能指标（如流量、磁盘IO、CPU使用率等）远大于该时刻前后时间段。毛刺的出现代表这服务器资源利用不均匀，不充分，容易诱发其他更严重的问题。&lt;/p&gt; &lt;h4&gt;重放攻击&lt;/h4&gt; &lt;p&gt;攻击者发送一个目的主机已接收过的包，来达到欺骗系统的目的，主要用于身份认证过程，破坏认证的正确性。它是一种攻击类型，这种攻击会不断恶意或欺诈性地重复一个有效的数据传输，重放攻击可以由发起者，也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据，之后再把它重新发给认证服务器。&lt;/p&gt; &lt;h4&gt;网络孤岛&lt;/h4&gt; &lt;p&gt;网络孤岛指集群环境中，部分机器与整个集群失去网络连接，分裂为一个小集群并且发生数据不一致的状况。&lt;/p&gt; &lt;h4&gt;数据倾斜&lt;/h4&gt; &lt;p&gt;对于集群系统，一般缓存是分布式的，即不同节点负责一定范围的缓存数据。我们把缓存数据分散度不够，导致大量的缓存数据集中到了一台或者几台服务节点上，称为数据倾斜。一般来说数据倾斜是由于负载均衡实施的效果不好引起的。&lt;/p&gt; &lt;h4&gt;脑裂&lt;/h4&gt; &lt;p&gt;脑裂是指在集群系统中，部分节点之间网络不可达而引起的系统分裂，不同分裂的小集群会按照各自的状态提供服务，原本的集群会同时存在不一致的反应，造成节点之间互相争抢资源，系统混乱，数据损坏。&lt;/p&gt; &lt;h2&gt;监控告警&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;服务监控&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;服务监控主要目的在服务出现问题或者快要出现问题时能够准确快速地发现以减小影响范围。服务监控一般有多种手段，按层次可划分为：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;系统层（CPU、网络状态、IO、机器负载等）&lt;/li&gt; &lt;li&gt;应用层（进程状态、错误日志、吞吐量等）&lt;/li&gt; &lt;li&gt;业务层（服务/接口的错误码、响应时间）&lt;/li&gt; &lt;li&gt;用户层（用户行为、舆情监控、前端埋点）&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;&lt;strong&gt;全链路监控&lt;/strong&gt;&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;**服务拨测：**服务拨测是探测服务（应用）可用性的监控方式，通过拨测节点对目标服务进行周期性探测，主要通过可用性和响应时间来度量，拨测节点通常有异地多个。&lt;/li&gt; &lt;li&gt;**节点探测：**节点探测是用来发现和追踪不同的机房（数据中心）节点之间网络可用性和通畅性的监控方式，主要通过响应时间、丢包率、跳数来度量，探测方法一般是ping、mtr或其他私有协议。&lt;/li&gt; &lt;li&gt;**告警过滤：**对某些可预知的告警进行过滤，不进入告警统计的数据，如少量爬虫访问导致的http响应500错误，业务系统自定义异常信息等。&lt;/li&gt; &lt;li&gt;**告警去重：**当一个告警通知负责人后，在这个告警恢复之前，不会继续收到相同的告警。&lt;/li&gt; &lt;li&gt;**告警抑制：**为了减少由于系统抖动带来的干扰，还需要实现抑制，例如服务器瞬间高负载，可能是正常的，只有持续一段时间的高负载才需要得到重视。&lt;/li&gt; &lt;li&gt;**告警恢复：**开发/运维人员不仅需要收到告警通知，还需要收到故障消除告警恢复正常的通知。&lt;/li&gt; &lt;li&gt;**告警合并：**对同一时刻产生的多条相同告警进行合并，如某个微服务集群同一时刻出现多个子服务负载过高的告警，需要合并成为一条告警。&lt;/li&gt; &lt;li&gt;**告警收敛：**有时某个告警产生时，往往会伴随着其它告警。这时可以只对根本原因产生告警，其它告警收敛为子告警一并发送通知。如云服务器出现CPU负载告警时往往伴随其搭载的所有系统的可用性告警。&lt;/li&gt; &lt;li&gt;**故障自愈：**实时发现告警，预诊断分析，自动恢复故障，并打通周边系统实现整个流程的闭环。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;服务治理&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;微服务&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;微服务架构是一种架构模式，它提倡将单一应用程序划分成一组小的服务，服务之间相互协调、互相配合，为用户提供最终价值。每个服务运行在其独立的进程中，服务和服务之间采用轻量级的通信机制相互沟通（通常是基于HTTP的Restful API).每个服务都围绕着具体的业务进行构建，并且能够被独立的部署到生产环境、类生产环境等。&lt;/p&gt; &lt;h4&gt;服务发现&lt;/h4&gt; &lt;p&gt;服务发现是指使用一个注册中心来记录分布式系统中的全部服务的信息，以便其他服务能够快速的找到这些已注册的服务。服务发现是支撑大规模 SOA 和微服务架构的核心模块，它应该尽量做到高可用。&lt;/p&gt; &lt;h4&gt;流量削峰&lt;/h4&gt; &lt;p&gt;如果观看抽奖或秒杀系统的请求监控曲线，你就会发现这类系统在活动开放的时间段内会出现一个波峰，而在活动未开放时，系统的请求量、机器负载一般都是比较平稳的。为了节省机器资源，我们不可能时时都提供最大化的资源能力来支持短时间的高峰请求。所以需要使用一些技术手段，来削弱瞬时的请求高峰，让系统吞吐量在高峰请求下保持可控。削峰也可用于消除毛刺，使服务器资源利用更加均衡和充分。常见的削峰策略有队列，限频，分层过滤，多级缓存等。&lt;/p&gt; &lt;h4&gt;版本兼容&lt;/h4&gt; &lt;p&gt;在升级版本的过程中，需要考虑升级版本后，新的数据结构是否能够理解和解析旧数据，新修改的协议是否能够理解旧的协议以及做出预期内合适的处理。这就需要在服务设计过程中做好版本兼容。&lt;/p&gt; &lt;h4&gt;过载保护&lt;/h4&gt; &lt;p&gt;过载是指当前负载已经超过了系统的最大处理能力，过载的出现，会导致部分服务不可用，如果处置不当，极有可能引起服务完全不可用，乃至雪崩。过载保护正是针对这种异常情况做的措施，防止出现服务完全不可用的现象。&lt;/p&gt; &lt;h4&gt;服务熔断&lt;/h4&gt; &lt;p&gt;服务熔断的作用类似于我们家用的保险丝，当某服务出现不可用或响应超时的情况时，为了防止整个系统出现雪崩，暂时停止对该服务的调用。&lt;/p&gt; &lt;h4&gt;服务降级&lt;/h4&gt; &lt;p&gt;服务降级是当服务器压力剧增的情况下，根据当前业务情况及流量对一些服务和页面有策略的降级，以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别，面临不同的异常等级执行不同的处理。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;根据服务方式：可以拒接服务，可以延迟服务，也有时候可以随机服务。&lt;/li&gt; &lt;li&gt;根据服务范围：可以砍掉某个功能，也可以砍掉某些模块。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是&lt;strong&gt;服务虽然有损但是总比没有好。&lt;/strong&gt;&lt;/p&gt; &lt;h4&gt;熔断VS降级&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;相同点：&lt;strong&gt;目标一致&lt;/strong&gt;，都是从可用性和可靠性出发，为了防止系统崩溃；&lt;strong&gt;用户体验类似&lt;/strong&gt;，最终都让用户体验到的是某些功能暂时不可用；&lt;/li&gt; &lt;li&gt;不同点：&lt;strong&gt;触发原因不同&lt;/strong&gt;，服务熔断一般是某个服务（下游服务）故障引起，而服务降级一般是从整体负荷考虑；&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;服务限流&lt;/h4&gt; &lt;p&gt;限流可以认为服务降级的一种，限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的，为了保证系统的稳定运行，一旦达到的需要限制的阈值，就需要限制流量并采取一些措施以完成限制流量的目的。比如：延迟处理，拒绝处理，或者部分拒绝处理等等。&lt;/p&gt; &lt;h4&gt;故障屏蔽&lt;/h4&gt; &lt;p&gt;将故障机器从集群剔除，以保证新的请求不会分发到故障机器。&lt;/p&gt; &lt;h2&gt;测试方法&lt;/h2&gt; &lt;h4&gt;&lt;strong&gt;黑盒/白盒测试&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;黑盒测试&lt;/strong&gt;不考虑程序内部结构和逻辑结构，主要是用来测试系统的功能是否满足需求规格说明书。一般会有一个输入值，一个输入值，和期望值做比较。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;白盒测试&lt;/strong&gt;主要应用在单元测试阶段，主要是对代码级的测试，针对程序内部逻辑结构，测试手段有：语句覆盖、判定覆盖、条件覆盖、路径覆盖、条件组合覆盖&lt;/p&gt; &lt;h4&gt;单元/集成/系统/验收测试&lt;/h4&gt; &lt;p&gt;软件测试一般分为4个阶段：单元测试、集成测试、系统测试、验收测试。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;**单元测试：**单元测试是对软件中的最小可验证单元进行检查和验证，如一个模块、一个过程、一个方法等。&lt;strong&gt;单元测试粒度最小&lt;/strong&gt;，一般由开发小组采用白盒方式来测试，主要测试单元是否符合“设计”。&lt;/li&gt; &lt;li&gt;**集成测试：**集成测试也叫做组装测试，通常在单元测试的基础上，将所有的程序模块进行有序的、递增的测试。&lt;strong&gt;集成测试界于单元测试和系统测试之间&lt;/strong&gt;，起到“桥梁作用”，一般由开发小组采用白盒加黑盒的方式来测试，既验证“设计”，又验证“需求”。&lt;/li&gt; &lt;li&gt;**系统测试：**系统测试时将经过集成测试的软件，作为计算机系统的一部分，与系统中其他部分结合起来，在实际运行环境下进行一系列严格有效的测试，以发现软件潜在的问题，保证系统的正常运行。&lt;strong&gt;系统测试的粒度最大&lt;/strong&gt;，一般由独立测试小组采用黑盒方式来测试，主要测试系统是否符合“需求规格说明书”。&lt;/li&gt; &lt;li&gt;**验收测试：**验收测试也称交付测试，是针对用户需求、业务流程进行的正式的测试，以确定系统是否满足验收标准，由用户、客户或其他授权机构决定是否接受系统。验收测试与系统测试相似，主要区别是测试人员不同，&lt;strong&gt;验收测试由用户执行。&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;回归测试&lt;/h4&gt; &lt;p&gt;当发现并修改缺陷后，或在软件中添加新的功能后，重新测试。用来检查被发现的缺陷是否被改正，并且所做的修改没有引发新的问题。&lt;/p&gt; &lt;h4&gt;冒烟测试&lt;/h4&gt; &lt;p&gt;这一术语源自硬件行业。对一个硬件或硬件组件进行更改或修复后，直接给设备加电。如果没有冒烟，则该组件就通过了测试。在软件中，“冒烟测试”这一术语描述的是在将代码更改嵌入到产品的源树中之前对这些更改进行验证的过程。&lt;/p&gt; &lt;p&gt;冒烟测试是在软件开发过程中的一种针对软件版本包的快速基本功能验证策略，是对软件基本功能进行确认验证的手段，并非对软件版本包的深入测试。&lt;/p&gt; &lt;p&gt;比如：对于一个登录系统的冒烟测试，我们只需测试输入正确的用户名、密码，验证登录这一个核心功能点，至于输入框、特殊字符等，可以在冒烟测试之后进行。&lt;/p&gt; &lt;h4&gt;性能测试&lt;/h4&gt; &lt;p&gt;性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。&lt;strong&gt;负载测试&lt;/strong&gt;和&lt;strong&gt;压力测试&lt;/strong&gt;都属于性能测试，两者可以结合进行。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;通过负载测试，确定在各种工作负载下系统的性能，目标是测试当负载逐渐增加时，系统各项性能指标的变化情况。&lt;/li&gt; &lt;li&gt;压力测试是通过确定一个系统的瓶颈或者不能接受的性能点，来获得系统能提供的最大服务级别的测试。&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;基准测试&lt;/h4&gt; &lt;p&gt;基准测试（Benchmark）也是一种性能测试方式，用来测量机器的硬件最高实际运行性能，以及软件优化的性能提升效果, 同时也可以用来识别某段代码的CPU或者内存效率问题. 许多开发人员会用基准测试来测试不同的并发模式, 或者用基准测试来辅助配置工作池的数量, 以保证能最大化系统的吞吐量.&lt;/p&gt; &lt;h4&gt;A/B测试&lt;/h4&gt; &lt;p&gt;A/B测试，是用两组及以上随机分配的、数量相似的样本进行对比，如果实验组和对比组的实验结果相比，在目标指标上具有统计显著性，那就可以说明实验组的功能可以导致你想要的结果，从而帮你验证假设或者做出产品决定。&lt;/p&gt; &lt;h4&gt;代码覆盖测试&lt;/h4&gt; &lt;p&gt;代码覆盖（Code coverage）是软件测试中的一种度量，描述程式中源代码被测试的比例和程度，所得比例称为代码覆盖率。在做单元测试时，代码覆盖率常常被拿来作为衡量测试好坏的指标，甚至，用代码覆盖率来考核测试任务完成情况，比如，代码覆盖率必须达到80%或 90%。于是乎，测试人员费尽心思设计案例覆盖代码。&lt;/p&gt; &lt;h2&gt;发布部署&lt;/h2&gt; &lt;h4&gt;DEV/PRO/FAT/UAT&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;pro（Production environment）：生产环境，面向外部用户的环境，正式环境，连接上互联网即可访问。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;sit(System Integration Test ): 系统集成测试，开发人员自己测试流程是否走通。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;uat(User Acceptance Test environment): 用户验收测试环境，用于生产环境下的软件测试者测试使用。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;test: 测试环境，外部用户无法访问，专门给测试人员使用的，版本相对稳定。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;pre ：灰度环境，外部用户可以访问，但是服务器配置相对低，其它和生产一样，外部用户可以访问，版本发布初期，正式版本发布前。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;dev （Development environment） ： 开发环境，外部用户无法访问，开发人员使用，版本变动很大。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;fat (Feature Acceptance Test environment) : 功能验收测试环境，用于软件测试者测试使用&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;灰度发布&lt;/h4&gt; &lt;p&gt;灰度发布是指在升级版本过程中，通过分区控制，白名单控制等方式对一部分用户先升级产品特性，而其余用户则保持不变，当一段时间后升级产品特性的用户没有反馈问题，就逐步扩大范围，最终向所有用户开放新版本特性，灰度发布可以保证整体系统的稳定，在初始灰度的时候就可以发现、修改问题，以保证其影响度。&lt;/p&gt; &lt;h4&gt;回滚 (Rollback)&lt;/h4&gt; &lt;p&gt;指的是程序或数据处理错误时，将程序或数据恢复到上一次正确状态(或者是上一个稳定版本)的行为。&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 28 Mar 2021 07:36:00 GMT</pubDate>
    </item>
    <item>
      <title>关于JDK源码：我想聊聊如何更高效地阅读</title>
      <link>https://maruifu.cn/article/187</link>
      <content:encoded>&lt;h2&gt;为什么要看JDK源码&lt;/h2&gt; &lt;p&gt;一，JDK源码是其它所有源码的&lt;strong&gt;基础&lt;/strong&gt;，看懂了JDK源码再看其它的源码会达到事半功倍的效果。&lt;/p&gt; &lt;p&gt;二，JDK源码中包含大量的&lt;strong&gt;数据结构&lt;/strong&gt;知识，是学习数据结构很好的资料，比如，链表、队列、散列表、红黑树、跳表、桶、堆、双端队列等。&lt;/p&gt; &lt;p&gt;三、JDK源码中包含大量的&lt;strong&gt;设计模式&lt;/strong&gt;，是学习设计模式很好的资料，比如，适配器模式、模板方法模式、装饰器模式、迭代器模式、代理模式、工厂模式、命令模式、状态模式等。&lt;/p&gt; &lt;p&gt;三，JDK源码中包含大量Java的&lt;strong&gt;高阶知识&lt;/strong&gt;，比如弱引用、Unsafe、CAS、锁原理、伪共享等，不看源码是很难学会这些知识的。&lt;/p&gt; &lt;p&gt;四，面试时更好地&lt;strong&gt;收割offer&lt;/strong&gt;，这可能是很多同学最初的想法，其实真正看多了源码，这一点可能并不是太重要了，因为你会发现更广阔的世界。&lt;/p&gt; &lt;p&gt;五，我认为最重要的，阅读源码是对思维的一种锻炼，是学习优秀设计的&lt;strong&gt;最佳途径&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;JDK源码的阅读顺序&lt;/h2&gt; &lt;p&gt;JDK 中的代码非常多，不可能、也没必要全部读完，因此要有的放矢。从整体上来讲，我分成了以下几个部分：&lt;/p&gt; &lt;h3&gt;基础类&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;基础类&lt;/strong&gt;，是指组成JDK源码地基的一部分类。&lt;/p&gt; &lt;p&gt;比如包装类、反射类、工具类等，这些类有个共同点，就是代码逻辑相对简单，不存在数据结构、复杂运算等问题。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于基础类&lt;/strong&gt;，我的建议是自己从头到尾浏览一遍，对于看不懂的地方可以写测试用例或者上网查查资料。比如，Integer里面有个IntegerCache内部类你可能不知道干嘛的，这时候光看代码是没用的，只能上网查查资料了，也不能盲目地死磕。&lt;/p&gt; &lt;h3&gt;简单集合&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;简单集合&lt;/strong&gt;，是指不存在多线程安全问题的集合。&lt;/p&gt; &lt;p&gt;这部分集合一般用在单线程中，或者方法体中，但是他们用到了很多的数据结构，所以需要一定的数据结构知识。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于简单集合&lt;/strong&gt;，我的建议是先弄明白底层的数据结构知识，再去看源码，这样可能会轻松一些。当然，我后面也会出数据结构系列的。&lt;/p&gt; &lt;h3&gt;原子类&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;原子类&lt;/strong&gt;，是指在多线程环境下能够保证原子性的类。&lt;/p&gt; &lt;p&gt;这部分类主要包括Atomic&lt;em&gt;开头和&lt;/em&gt;Adder结尾的类，位于juc下面的atomic包中。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于原子类&lt;/strong&gt;，我的建议是先去了解底层的Unsafe、CAS、伪共享等概念，再去看最简单的AtomicInteger，最后再看LongAdder这种复杂的类。其中，断点调试是不可或缺的手段。&lt;/p&gt; &lt;p&gt;说句实话，LongAdder这个类能学到很多高阶的知识，非常推荐把这个类研究透彻，后面再去看Disruptor、Netty等源码会事半功倍。&lt;/p&gt; &lt;h3&gt;同步器&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;同步器&lt;/strong&gt;，是指为了控制多个线程的竞争关系而存在的类或者关键字等， ，它们可以说是Java中最重要的内容，没有它们就无法控制多线程的正常运转。&lt;/p&gt; &lt;p&gt;这部分内容主要包括synchronized关键字、volatile关键字、重入锁、读写锁、倒计时器、信号量、回环栅栏、阶段器、分布式锁的实现等等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于同步器&lt;/strong&gt;，我的建议是先了解内存模型、可见性、原子性、有序性、Happens-Before等基本概念，再尝试阅读这部分的源码，最后再归纳出属于你自己理解的“同步器的原理”。&lt;/p&gt; &lt;h3&gt;并发集合&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;并发集合&lt;/strong&gt;，是指多线程环境下能够保证数据一致性的集合。&lt;/p&gt; &lt;p&gt;这部分集合主要是运用在多线程环境下，只有极个别类牵涉到高级的数据结构，更多的是锁、CAS、volatile、自旋等高阶技巧的运用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于并发集合&lt;/strong&gt;，我的建议有三点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;一定要在同步器之后阅读&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据结构先搞透，比如ConcurrentSkipList&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;利用IDEA的Thread级别的断点，不断调试，不断调试，不断调试&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;线程（池）类&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;线程（池）类&lt;/strong&gt;，是指跟线程和线程池相关的类。&lt;/p&gt; &lt;p&gt;这部分类主要包含Thread、ThreadLocal、三种线程池等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;对于线程（池）类&lt;/strong&gt;，我的建议是先从整体上把握，再分成几个块来看，看哪块的东西就只看那块的东西，不要管其它的代码，即要搞清楚你的重点在哪里，比如，看线程运行的流程就不要管状态的事，凡是牵涉到状态的代码全部跳过，反之亦然，都看完了，再串一起看。&lt;/p&gt; &lt;h3&gt;IO/NIO类&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;IO类&lt;/strong&gt;，是指跟输入输出流相关的类，这部分类主要包括文件操作相关的类以及网络IO相关的类。&lt;/p&gt; &lt;p&gt;对于IO类，我的建议是简单浏览，做到心里有数即可，用到的时候再去查都可以。&lt;/p&gt; &lt;p&gt;但是对于nio相关的类，还是要好好研究的，这部分类我们放在Netty源码阅读的相关章节中一起学习。&lt;/p&gt; &lt;h3&gt;其它类&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;其它类&lt;/strong&gt;，工作中遇到了可以点进去看看，但是不建议抽出时间单独去研究，比如，时间类、awt类，看的必要性不是很大。&lt;/p&gt; &lt;h2&gt;如何阅读集合&lt;/h2&gt; &lt;p&gt;首页了解各个集合特点，可以画个思维导图或者啥的助于理解&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/30/b340dc73ee631675e8c667bdc07f4207.png" alt="集合" title="集合" /&gt;&lt;/p&gt; &lt;h2&gt;如何阅读具体一个类&lt;/h2&gt; &lt;p&gt;如何阅读一个类的源码呢？主要步骤大概是：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;先读接口代码。包括接口说明文档、各个方法的定义和说明文档。&lt;/li&gt; &lt;li&gt;再读实现类的主要方法实现，通常有以下两条主线入口：构造方法和 常用方法&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;在 Java 中，接口通常意味着是一种“标准”、或者“协议”。一个接口可以有多个实现类，它们都会按照接口的这种标准来实现接口的各个方法。因此，理解了一个方法的定义，再去看它的实现会更容易理解。&lt;/p&gt; &lt;p&gt;下面以常用的 ArrayList 为例，分析如何去阅读它的源码。&lt;/p&gt; &lt;h4&gt;继承结构&lt;/h4&gt; &lt;p&gt;首先看下 ArrayList 的继承结构：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/30/image-20221130103419456.png" alt="image-20221130103419456" title="image-20221130103419456" /&gt;&lt;/p&gt; &lt;p&gt;ArrayList：可以看到它实现了很多接口，其中三个接口 Cloneable、RandomAccess、Serializable 都是空的，可以暂时忽略。主要去看 Iterable、Collection 以及 List 接口的方法定义。&lt;/p&gt; &lt;p&gt;Iterable 接口：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/30/image-20221130103735811.png" alt="image-20221130103735811" title="image-20221130103735811" /&gt;&lt;/p&gt; &lt;p&gt;Collection 接口：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/30/image-20221130103815292.png" alt="image-20221130103815292" title="image-20221130103815292" /&gt;&lt;/p&gt; &lt;p&gt;List 接口：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/11/30/image-20221130103921453.png" alt="image-20221130103921453" title="image-20221130103921453" /&gt;&lt;/p&gt; &lt;p&gt;看起来方法挺多，其实不少都是我们平时会用到的，大部分理解起来并不困难，而且方法也都有注释。这部分难度不大。&lt;/p&gt; &lt;p&gt;接下来根据前面提到的两条主线入口，分析 ArrayList 的源码如何阅读。&lt;/p&gt; &lt;h4&gt;构造器&lt;/h4&gt; &lt;p&gt;分析一个类的源码时，构造器通常是一个好的切入点。比如 ArrayList 的三个构造器如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public ArrayList() {     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }  public ArrayList(int initialCapacity) {     if (initialCapacity &amp;gt; 0) {         this.elementData = new Object[initialCapacity];     } else if (initialCapacity == 0) {         this.elementData = EMPTY_ELEMENTDATA;     } else {         throw new IllegalArgumentException(&amp;quot;Illegal Capacity: &amp;quot;+                                            initialCapacity);     } }  public ArrayList(Collection&amp;lt;? extends E&amp;gt; c) {     elementData = c.toArray();     if ((size = elementData.length) != 0) {         // c.toArray might (incorrectly) not return Object[] (see 6260652)         if (elementData.getClass() != Object[].class)             elementData = Arrays.copyOf(elementData, size, Object[].class);     } else {         // replace with empty array.         this.elementData = EMPTY_ELEMENTDATA;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;构造器中有不少成员变量，比如 elementData、EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 等，继续跟进这几个变量：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;private static final Object[] EMPTY_ELEMENTDATA = {};  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  transient Object[] elementData; // non-private to simplify nested class access &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;由此可以得知，当我们写了 &lt;code&gt;new ArrayList()&lt;/code&gt; 时，它的内部到底做了些什么。&lt;/p&gt; &lt;h4&gt;常用方法&lt;/h4&gt; &lt;p&gt;除了构造器，常用方法也是一个主要的入口，比如 add、remove 等。&lt;/p&gt; &lt;p&gt;add 方法实现：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public boolean add(E e) {     ensureCapacityInternal(size + 1);  // Increments modCount!!     elementData[size++] = e;     return true; }  private void ensureCapacityInternal(int minCapacity) {     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以一行行跟进代码，查看 add 方法内部到底做了什么。 其他方法的套路也是如此，不再一一说明。 按照这样一条条主线走下来，就可以对 ArrayList 的实现原理有个整体的认知了。整体部分搞清楚之后，接下来还可以去读一些不太常用的方法，包括剩余的所有部分。&lt;/p&gt; &lt;h2&gt;JDK源码的阅读方法&lt;/h2&gt; &lt;p&gt;一，&lt;strong&gt;设定目标&lt;/strong&gt;，目标越明确越好，不要设定得过于虚无缥缈。比如，熟悉HashMap的数据结构，这就是一个很明确的目标；再比如，看懂HashMap的源码，这就很缥缈了。&lt;/p&gt; &lt;p&gt;二，&lt;strong&gt;尝试自己提出问题&lt;/strong&gt;，先自己根据某个知识点发散提出问题。比如，关于HashMap你能想到哪些知识点，这部分可以借助思维导图无限想象，后面有机会我给大家分享一下思维导图联想法。&lt;/p&gt; &lt;p&gt;三，&lt;strong&gt;尝试网络查询问题&lt;/strong&gt;，打开度娘，输入你要学习的知识点，把前面几页统统点开，看看别人都遇到了哪些问题，当然，能力强的同学也可以使用Google，这部分查询出来的问题也可以补充到你的思维导图中去。&lt;/p&gt; &lt;p&gt;四，&lt;strong&gt;尝试阅读源码&lt;/strong&gt;，对于上面的问题，一个一个尝试去源码中寻找答案，由点及面，最后再总结整个大的知识点。&lt;/p&gt; &lt;p&gt;五，&lt;strong&gt;不断发现问题&lt;/strong&gt;，在阅读源码的过程中可能又会发现新的问题，先跳过去，而是把它加到思维导图中，等当前的问题解决完了再去解决。&lt;/p&gt; &lt;p&gt;六，&lt;strong&gt;专注你的问题&lt;/strong&gt;，在阅读源码的时候一定要专注于你当前的问题，不要受其它问题的干扰，比如看线程池任务执行的流程，你就不要管线程池状态的事情。&lt;/p&gt; &lt;p&gt;七，&lt;strong&gt;多做比较&lt;/strong&gt;，横向比较和纵向比较，从多维度去比较 。&lt;/p&gt; &lt;p&gt;八，&lt;strong&gt;多做实验&lt;/strong&gt;，多多利用IDE的调试模式，不断修改断点，不断调试。&lt;/p&gt; &lt;p&gt;九，&lt;strong&gt;多与人交流&lt;/strong&gt;，如果条件允许的话，多与周边的人一起交流，当然，也可以来骚扰我。&lt;/p&gt; &lt;p&gt;十，&lt;strong&gt;多做总结&lt;/strong&gt;，对于自己解决的问题，一定要学会总结，多做学习笔记，当然，也欢迎来我这里投稿。&lt;/p&gt; &lt;p&gt;十一，&lt;strong&gt;耐心&amp;amp;坚持&lt;/strong&gt;，阅读源码是一件非常枯燥而且枯燥的事情，一定要坚持坚持坚持。&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 22 Mar 2021 01:30:00 GMT</pubDate>
    </item>
    <item>
      <title>Idea 打包成功，运行失败</title>
      <link>https://maruifu.cn/article/186</link>
      <content:encoded>&lt;p&gt;最近开发中，发现一个奇怪的问题，用idea 的maven ，编译，打包都能够成功，项目中也没有爆红出错，但是你运行/调式SpringBoot的启动类的时候，会提示包**不存在。网上找了很多解决方案 都没成功。最后终于成功了，特此记录分享下：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Idea Terminal 中 输入 &lt;code&gt;mvn idea:idea&lt;/code&gt;&lt;/li&gt; &lt;li&gt;File-Invadiate Cache/Restart ，清除缓存，重启&lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Thu, 18 Mar 2021 01:36:00 GMT</pubDate>
    </item>
    <item>
      <title>springboot自动判定空值</title>
      <link>https://maruifu.cn/article/185</link>
      <content:encoded>&lt;p&gt;Spring Boot 参数校验&lt;/p&gt; &lt;h2&gt;前言&lt;/h2&gt; &lt;p&gt;搭建springboot项目，我们都是采用的Restful接口，那么问题来了，当前端调用接口或者是其他项目调用时，我们不能单一靠调用方来控制参数的准确性，自己也要对一些非空的 值进行判定。&lt;/p&gt; &lt;h2&gt;方案&lt;/h2&gt; &lt;p&gt;按照我们以往的做法，都是对request中的参数一个一个进行非空判定。&lt;/p&gt; &lt;p&gt;Model:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;publicclassOrder{  private Long userID;  private Long addressID;  private String comment; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Controller:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;@PostMapping(&amp;quot;/createOrders&amp;quot;) publicStringcreateOrders(@RequestBodyOrderdto){   if(dto.getUserID==null){        return &amp;quot;userID不能为空&amp;quot;;     }   if(dto.getAddressID==null){         return &amp;quot;addressID不能为空&amp;quot;;     }   if(dto.getComment==null){         return &amp;quot;comment不能为空&amp;quot;;     }   return &amp;quot;sucess&amp;quot;;  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这种做法首先是可取的，能达到我们的要求，但是这样如果model字段过多，判定的就很 多，相对维护起来就不是那么方便，其次增加controller层的负担，既然我们来到spring4 的时代，就应该适应使用注解的趋势，下面是使用注解后的比变化。&lt;/p&gt; &lt;p&gt;Model:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;publicclassOrder{   @NotNull(message = &amp;quot;用户ID不能为空&amp;quot;)  private Long userID;   @NotNull(message = &amp;quot;收货人地址id不能为空&amp;quot;)  private Long addressID;   @NotBlank(message = &amp;quot;备注不为空&amp;quot;)  private String comment; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Controller:&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;@PostMapping(&amp;quot;/createOrders&amp;quot;) publicStringcreateOrders(@RequestBody @Valid Order dto,BindingResult results) {    if (results.hasErrors()){    return results.getFieldError().getDefaultMessage();           }   return &amp;quot;success&amp;quot;;  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这样我们就只需要在model字段上加上非空验证和相应提示语就好了。 备注:@Valid 和@Validated效果一样，可以加在controller中，也可以加载dto上&lt;/p&gt; &lt;h2&gt;常用的校验注解&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;1. javax.validation.constraints.NotNull  2. @Null 被注释的元素必须为null 3. @NotNull 被注释的元素不能为null  4. @AssertTrue 被注释的元素必须为true 5. @AssertFalse 被注释的元素必须为false  6. @Min(value) 被注释的元素必须是一个数字，其值必须小于等于指定的最小值  7. @Max(value) 被注释的元素必须是一个数字，其值必须大于等于指定的最大值 8. @DecimalMin(value) 被注释的元素必须是一个数字，其值必须大于等于指定的 最小值 9. @DecimalMax(value) 被注释的元素必须是一个数字，其值必须小于等于指定的 最大值 10. @Size(max,min) 被注释的元素的大小必须在指定的范围内。 11. @Digits(integer,fraction) 被注释的元素必须是一个数字，其值必须在可接 受的范围内 12. @Past 被注释的元素必须是一个过去的日期 13. @Future 被注释的元素必须是一个将来的日期 14. @Pattern(regexp) 被注释的元素必须符合指定的正则表达式。 15. @Email 被注释的元素必须是电子邮件地址 16. @Length 被注释的字符串的大小必须在指定的范围内 17. @NotEmpty 被注释的字符串必须非空 18. @Range 被注释的元素必须在合适的范围内 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;其他&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;@Valid 注解类型的使用: @Null 限制只能为null @NotNull 限制必须不为null @AssertFalse 限制必须为false, @AssertTrue 限制必须为true, @DecimalMax(value) 限制必须为一个不大于指定值的数字 @DecimalMin(value) 限制必须为一个不小于指定值的数字 @Digits(integer,fraction) 限制必须为一个小数，且整数部分的位数不能超过integer，小数部分的位数不能超过 fraction @Future 限制必须是一个将来的日期 @Max(value) 限制必须为一个不大于指定值的数字 @Min(value) 限制必须为一个不小于指定值的数字 @Past 限制必须是一个过去的日期 @Pattern(value) 限制必须符合指定的正则表达式 @Size(max,min) 限制字符长度必须在min到max之间 @Past 验证注解的元素值(日期类型)比当前时间早 @NotEmpty  验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)  @NotBlank  验证注解的元素值不为空(不为null、去除首位空格后长度为0)，不同于@NotEmpty， @NotBlank只应用于字符串且在比较时会去除字符串的空格 @Email 验证注解的元素值是Email，也可以通过正则表达式和flag指定自定义的email格式 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;问题&lt;/h2&gt; &lt;p&gt;@NotBlank无效 可能你为了使用@NotBlank引入了包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;jakarta.validation&amp;lt;/groupId&amp;gt;  &amp;lt;artifactId&amp;gt;jakarta.validation‐api&amp;lt;/arti  &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;之所以需要引入这个包，是因为你的spring boot 版本是2.3.1或者更高，此时的spring boot 已经不在内置验证。 此时需要引入包 哪怕与@Valid搭配也是没有效果，大概率是因为我们少导入了一个包hibernate- validator，我们需要同时导入以下两个包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring‐boot‐starter‐validation&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt;  &amp;lt;!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator --&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;org.hibernate.validator&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;hibernate-validator&amp;lt;/artifactId&amp;gt;   &amp;lt;version&amp;gt;6.0.2.Final&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;或者是不导入以上两个包，直接将spring boot修改为2.1.1均可以解决此问题&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring‐boot‐starter‐web&amp;lt;/artifactId&amp;gt;   &amp;lt;version&amp;gt;2.1.1.RELEASE&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;扩展&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;1.@NotNull:不能为null，但可以为empty  (&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot; &amp;quot;)   2.@NotEmpty:不能为null，而且长度必须大于0  (&amp;quot; &amp;quot;,&amp;quot; &amp;quot;)  3.@NotBlank:只能作用在String上，不能为null，而且调用trim()后，长度必须大于0(&amp;quot;test&amp;quot;) 即:必须有实际字符   @NotNull:The CharSequence,Collection,Map or Array object is not null,but can be empty.  @NotEmpty:The CharSequence,Collection,Map or Array object is not null and size &amp;gt; 0 .  @NotBlank:The string is not null and the trimmed length is greater than zero.  Stringname=null; @NotNull:false @NotEmpty:false @NotBlank:false  Stringname=&amp;quot;&amp;quot;; @NotNull:true @NotEmpty:false @NotBlank:false  Stringname=&amp;quot; &amp;quot;; @NotNull:true @NotEmpty:true @NotBlank:false  Stringname=&amp;quot;Greatanswer!&amp;quot;; @NotNull:true @NotEmpty:true @NotBlank:true &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 25 Feb 2021 08:23:37 GMT</pubDate>
    </item>
    <item>
      <title>Mysql配置详解</title>
      <link>https://maruifu.cn/article/184</link>
      <content:encoded>&lt;h2&gt;一、安装&lt;/h2&gt; &lt;h3&gt;1、docker 安装&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;DOCKER_NAME=mysql MYSQL_ROOT_PASSWORD=78Jikbfz6zKYfPjC # 创建挂载目录 mkdir -p /data/$DOCKER_NAME mkdir -p /data/$DOCKER_NAME/conf mkdir -p /data/$DOCKER_NAME/data mkdir -p  /data/$DOCKER_NAME/sql  # 编写初始化sql vi /data/$DOCKER_NAME/sql/init.sql  # 编写mysql配置文件 vi /data/$DOCKER_NAME/conf/my.cnf  # 下载容器镜像 # docker search mysql docker pull mysql:5.7  # 运行容器 docker run --name $DOCKER_NAME \ --restart=always \ -p 3306:3306 \ -v /data/$DOCKER_NAME/conf:/etc/mysql \ -v /data/$DOCKER_NAME/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD \ -d mysql:5.7  # 执行初始化脚本 mysql -uroot -p$MYSQL_ROOT_PASSWORD -h 127.0.0.1 -P 3306 &amp;lt; /data/$DOCKER_NAME/sql/init.sql  # 登陆验证 mysql -uroot -p$MYSQL_ROOT_PASSWORD -h 127.0.0.1 -P 33307 mysql&amp;gt; show databases ; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;2、编译安装&lt;/h3&gt; &lt;h3&gt;3、Yum安装&lt;/h3&gt; &lt;h2&gt;二、配置文件&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 客户端登录配置 [client] port = 3306 # 端口号 socket = /var/lib/mysql/mysql.sock # 套接字文件  # 客户端命令行配置 [mysql] no-auto-rehash # 默认不自动补全 auto-rehash自动补全  # 服务优化配置 [mysqld] skip-grant-tables # 跳过登录验证 user = mysql # 默认启动用户，一般不需要修改，可能出现启动不成功 port = 3306 # 端口号 socket = /var/lib/mysql/mysql.sock # 套接字文件 （套接字方式登陆比TCP/IP方式连接快） character-set-server = utf8mb4 # 设置数据库服务器默认编码 utf-8 basedir = /usr/local/mysql # 数据库安装目录--指定此参数可解决相对路径造成的问题 datadir = /var/lib/mysql #数据库目录，数据库目录切换时需要用到 pid-file = /var/run/mysqld/mysqld.pid #mysql进程文件，可指定自己的进程文件 external-locking = FALSE #外部锁定(非多服务器可不设置该选项，默认skip-external-locking) skip-external-locking #跳过外部锁定 （避免多进程环境下的external locking--锁机制） skip-name-resolve = 1 #跳过主机名解析，直接IP访问，可提升访问速度 log-error = /data/log/mysqld_error.log #错误日志文件  # 重要配置 max_connections = 5000 # 最大连接数 max_connect_errors = 6000 # 客户端请求异常中断次数 max_allowed_packet = 32M # 限制单条数据大小 sort_buffer_size = 8M # 每个连接独享内存数，如：500连接 * 8 = 4G 内存 join_buffer_size = 8M # 表关联缓存大小，每个连接独享  # 数据库引擎相关参数 default-storage-engine = InnoDB # 默认数据库引擎  # 性能分析 slow-query-log = 1 # 是否记录慢查询日志 long_query_time = 2 # 慢查询超时时间设置 slow-query-log-file=/var/log/mysql/query-slow.log #慢查询日志记录文件  # 二进制文件设置 log_bin = = /data/log/mysql/bin-log.log # 开启bin-log日志，指定存储路径 binlog_format = ROW # ROW(基于行的复制--安全，但是注意并发) STATEMENT(基于sql语句的复制)，MIXED(混合模式复制) binlog_cache_size = 4M # 二进制日志缓存，提高log-bin记录效率 log_bin_trust_function_creators = 1 #主从复制是需要注意，为了保证主从复制完全一致，需要开启此选项，主从默认阻止函数创建 max_binlog_size = 1G # 二进制日志文件大小默认1G 要求大于4096 小于1G expire_logs_days = 7 # 清除过期日志  # 主从复制相关 server-id = 2020 #主从复制必须，并且各服务器具有唯一性 log_slave_updates #配置从服务器的更新是否写入二进制日志，默认是不打开的 replicate-ignore-db = mysql #主从复制默认忽略的数据库，可用&amp;quot;,&amp;quot;分隔或使用多条记录 # replicate-do-db=qrs,login #主从复制指定数据库,&amp;quot;,&amp;quot;号隔开或使用多条记录  #数据库全量备份 [mysqldump]  quick #强制mysqldump从服务器一次一行地检索表中的行 max_allowed_packet = 32M #可接收数据包大小  [isamchk] #在mysqld服务器不使用的情况下修复表或在崩溃状态下恢复表 key_buffer = 1024M sort_buff_size =1024M read_buffer = 16M write_buffer = 16M  [myisamchk] #在mysqld服务器不使用的情况下修复表或在崩溃状态下恢复表 key_buffer = 1024M  sort_buff_size = 1024M read_buffer = 16M write_buffer = 16M  [mysqld_safe] #safe方式启动数据库，相比于mysqld,会在服务启动后继续监控服务状态，死机时重启 open-files-limit = 8192  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;三、sql语句&lt;/h2&gt; &lt;h2&gt;四、备份&lt;/h2&gt; &lt;h3&gt;1、mysqldump&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;#!/bin/bash  # 建造AR-mysql server_host= server_port= server_user= server_passwd= back_date=`date &amp;quot;+%Y-%m-%d&amp;quot;` back_time=`date &amp;quot;+%H:%M:%S&amp;quot;` back_path=/data/backup/$back_date/mysql  # 切换到备份目录 mkdir -p $back_path cd $back_path   # db列表(过滤不备份库) db_list=`mysql -h $server_host \ -P $server_port \ -u$server_user \ -p$server_passwd \ -e &amp;quot;show databases;&amp;quot; \ | grep -Ev &amp;quot;Database|information_schema|mysql|test|performance_schema|sys&amp;quot; `  # 遍历备份 for db in $db_list;do sql_name=${db}_${back_time}.sql mysqldump -h $server_host \ -P $server_port \ -u$server_user \ -p$server_passwd \ --databases $db &amp;gt; $sql_name done  # 删除过期文件 find /data/backup/ -mindepth 2 -type d -mtime +3 -exec rm -rf {} \; &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;--all-databases \ # 备份所有服务器 --compact \ # 压缩模式 --comments \ # 添加注释信息 --complete-insert \ # 输出完成的插入语句 --lock-tables \ # 备份前，锁定所有数据库表 --no-create-db | --no-create-info \ # 禁止生成创建数据库语句 --force \ # 当出现错误时仍然继续备份操作 --default-character-set \ # 指定默认字符集 --add-locks  --no-data \ # 不到处 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 23 Feb 2021 02:05:00 GMT</pubDate>
    </item>
    <item>
      <title>minio安装使用</title>
      <link>https://maruifu.cn/article/183</link>
      <content:encoded>&lt;h2&gt;一、安装&lt;/h2&gt; &lt;h3&gt;docker&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;docker pull minio/minio:latest  DOCKER_NAME=minio MINIO_ACCESS_KEY=admin MINIO_SECRET_KEY=admin123  mkdir -p /data/$DOCKER_NAME/data mkdir -p /data/$DOCKER_NAME/conf  docker run --name $DOCKER_NAME \ -p 9000:9000 \ -d \ -e &amp;quot;MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY&amp;quot; \ -e &amp;quot;MINIO_SECRET_KEY=$MINIO_SECRET_KEY&amp;quot; \ -v /data/$DOCKER_NAME/data:/data \ -v /data/$DOCKER_NAME/conf:/root/.minio \ minio/minio server /data   # 客户端 docker pull minio/mc  docker run -it --entrypoint=/bin/sh minio/mc  mc config host add myminio http://192.168.0.1:9000 $MINIO_ACCESS_KEY  $MINIO_SECRET_KEY   # 配置指定桶开放下载 mc policy set public myminio/backup   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;源码&lt;/h3&gt; &lt;p&gt;...&lt;/p&gt; &lt;hr /&gt; &lt;h2&gt;二、mc客户端使用&lt;/h2&gt; &lt;h3&gt;bucket桶策略&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-json"&gt;cat &amp;gt; getonly.json &amp;lt;&amp;lt; EOF {   &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,    &amp;quot;Statement&amp;quot;: [     {       &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,       &amp;quot;Action&amp;quot;: [            //  可以做出的行动（权限）         &amp;quot;s3:ListAllMyBuckets&amp;quot;,          //  查看所有的“桶”列表     &amp;quot;s3:ListBucket&amp;quot;,                //  查看桶内的对象列表     &amp;quot;s3:GetBucketLocation&amp;quot;,              &amp;quot;s3:GetObject&amp;quot;,                //  下载对象     &amp;quot;s3:PutObject&amp;quot;,                //  上传对象     &amp;quot;s3:DeleteObject&amp;quot;               //  删除对象                ],       &amp;quot;Resource&amp;quot;: [         &amp;quot;arn:aws:s3:::my-bucketname/*&amp;quot;  // （应用到的资源，*表示所有，也可以用路径来控制范围。arn:aws:s3是命名空间）       ],       &amp;quot;Sid&amp;quot;: &amp;quot;&amp;quot;     }   ] } EOF &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 添加该策略 mc admin policy add myminio getonly getonly.json &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;管理用户&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 创建用户 mc admin user add myminio newuser password  # 授权策略 mc admin policy set myminio getonly user=newuser  # 禁用用户 mc admin user disable myminio newuser  # 列出所有用户 mc admin user list myminio  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;管理组&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 创建组 mc admin group add myminio newgroup newuser  # 授权策略 mc admin policy set myminio getonly group=newgroup  #     # 禁用组 mc admin group disable myminio newgroup  # 列出所有组  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;三、使用问题&lt;/h2&gt; &lt;h3&gt;1、分享链接无法访问&lt;/h3&gt; &lt;h4&gt;&lt;code&gt;nginx代理配置&lt;/code&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-nginx"&gt; server {   listen 80;   server_name minio.maruifu.cn;   location / {     proxy_set_header Host $http_host;     proxy_set_header X-Real-IP $remote_addr;     proxy_set_header X-Forwarded-Proto $scheme;     proxy_set_header X-Forwarded-Host $http_host;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_pass http://127.0.0.1:9000;   } } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;&lt;code&gt;配置bucket访问权限&lt;/code&gt;&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;minio默认bucket无读取和写入权限&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;1、点击三个点按钮&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/23/img_108bf99815fb8ad3c.png" alt="img_1" title="img_1" /&gt;&lt;/p&gt; &lt;p&gt;2、编辑策略&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/23/img_2.png" alt="img_2" title="img_2" /&gt;&lt;/p&gt; &lt;p&gt;3、添加权限&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/23/img_3.png" alt="img_3" title="img_3" /&gt;&lt;/p&gt; &lt;hr /&gt; &lt;h2&gt;三、API&lt;/h2&gt; &lt;h3&gt;1、golang&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;文档地址：&lt;code&gt;https://docs.minio.io/docs/golang-client-quickstart-guide.html&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;code&gt;连接minio服务器&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;package main  import (  &amp;quot;log&amp;quot;   &amp;quot;github.com/minio/minio-go/v7&amp;quot;  &amp;quot;github.com/minio/minio-go/v7/pkg/credentials&amp;quot; )  func main() {  endpoint := &amp;quot;minio.dongzi.online&amp;quot;  accessKeyID := &amp;quot;dongdonghe&amp;quot;   // ID  secretAccessKey := &amp;quot;19981014&amp;quot; // Secret  useSSL := false               // ssl配置   // Initialize minio client object.  minioClient, err := minio.New(endpoint, &amp;amp;minio.Options{   Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, &amp;quot;&amp;quot;),   Secure: useSSL,  })  if err != nil {   log.Fatalln(err)  }  log.Printf(&amp;quot;%#v\n&amp;quot;, minioClient) // minioClient is now setup } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;创建bucket&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func createBucket(mc *minio.Client, bucketName string) (err error) {  err = mc.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: &amp;quot;us-east-1&amp;quot;, ObjectLocking: false}) // Region : 存储桶（us-east-1：默认）  if err != nil {   fmt.Println(err)   return  }  fmt.Println(&amp;quot;Successfully created mybucket.&amp;quot;)  return } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;列出所有bucket&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func listBucket(mc *minio.Client) (err error) {  buckets, err := mc.ListBuckets(context.Background())  if err != nil {   fmt.Println(err)   return  }  for _, bucket := range buckets {   fmt.Printf(&amp;quot;bucket名称：%s，创建时间：%s\n&amp;quot;, bucket.Name, bucket.CreationDate)  }  return } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;检查bucket是否存在&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func bucketExits(mc *minio.Client, bucketName string) (err error) {  found, err := mc.BucketExists(context.Background(), bucketName)  if err != nil {   fmt.Println(err)   return  }  if found {   fmt.Println(&amp;quot;Bucket found&amp;quot;)  } else {   fmt.Println(&amp;quot;Bucket not found&amp;quot;)  }  return } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;删除bucket&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func deleteBucket(mc *minio.Client, bucketName string) (err error) {  err = mc.RemoveBucket(context.Background(), bucketName)  if err != nil {   fmt.Println(err)   return  }  return } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;获取bucket文件对象列表&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func listObjs(mc *minio.Client, bucketName string) (err error) {  ctx, cancel := context.WithCancel(context.Background())  defer cancel()  objectCh := mc.ListObjects(ctx, bucketName, minio.ListObjectsOptions{   Prefix:    &amp;quot;制图&amp;quot;, // 前缀条件   Recursive: true, // 循环  })  for object := range objectCh {   if object.Err != nil {    fmt.Println(object.Err)    return   }   fmt.Printf(&amp;quot;文件名：%s，大小：%verb&amp;quot;, object.Key, object.Size)  }  return } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;上传文件&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-go"&gt;func uploadObject(mc *minio.Client, bucketName, objectName string, filePath string, contentType string) {  n, err := mc.FPutObject(context.Background(),   bucketName, // bucket   objectName, // 上传文件名称   filePath,   // 上传文件地址   minio.PutObjectOptions{ContentType: contentType}) // 上传文件类型  if err != nil {   log.Fatalln(err)  }   log.Printf(&amp;quot;Successfully uploaded %s of size %d byte \n&amp;quot;, objectName, n.Size) } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 23 Feb 2021 02:02:00 GMT</pubDate>
    </item>
    <item>
      <title>Gateway--服务网关</title>
      <link>https://maruifu.cn/article/182</link>
      <content:encoded>&lt;h2&gt;网关简介&lt;/h2&gt; &lt;p&gt;大家都都知道在微服务架构中，一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么 多的微服务呢?如果没有网关的存在，我们只能在客户端记录每个微服务的地址，然后分别去调用。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-01-3.50.40.png" alt="网关简介" title="网关简介" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;这样的架构，会存在着诸多的问题:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;客户端多次请求不同的微服务，增加客户端代码或配置编写的复杂性&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;认证复杂，每个服务都需要独立认证。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;存在跨域请求，在一定场景下处理相对复杂。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;上面的这些问题可以借助&lt;strong&gt;API网关&lt;/strong&gt;来解决。 所谓的API网关，就是指系统的&lt;strong&gt;统一入口&lt;/strong&gt;，它封装了应用程序的内部结构，为客户端提供统一服务，一些与业务本身功能无关的公共逻辑可以在这里实现，诸如认证、鉴权、监控、路由转发等等。 添加上API网关之后，系统的架构图变成了如下所示:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/image-20210201155346655.png" alt="API网关" title="API网关" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;我们也可以观察下，我们现在的整体架构图:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/image-20210201155428549.png" alt="整体架构图" title="整体架构图" /&gt;&lt;/p&gt; &lt;p&gt;在业界比较流行的网关，有下面这些:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Ngnix+lua&lt;/strong&gt;:使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用，lua是一 种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Kong&lt;/strong&gt;:基于Nginx+Lua开发，性能高，稳定，有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:只支持Http协议;二次开发，自由扩展困难;提供管理API，缺乏更易用的管控、配置 方式。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Zuul&lt;/strong&gt; :Netflflix开源的网关，功能丰富，使用JAVA开发，易于二次开发 问题:缺乏管控，无法动 态配置;依赖组件较多;处理Http请求依赖的是Web容器，性能不如Nginx&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Spring Cloud Gateway&lt;/strong&gt;:Spring公司为了替换Zuul而开发的网关服务，将在下面具体介绍。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;**注意:**SpringCloud alibaba技术栈中并没有提供自己的网关，我们可以采用Spring Cloud Gateway来做网关&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;Gateway简介&lt;/h2&gt; &lt;p&gt;Spring Cloud Gateway是Spring公司基于Spring 5.0，Spring Boot 2.0 和 Project Reactor 等技术开发 的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflflix Zuul，其不仅提供统一的路由方式，并且基于 Filter 链的方式提供了网关基本的功能，例如:安全，监 控和限流。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;性能强劲:是第一代网关Zuul的1.6倍&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;功能强大:内置了很多实用的功能，例如转发、监控、限流等&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;设计优雅，容易扩展&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;其实现依赖Netty与WebFlux，不是传统的Servlet编程模型，学习成本高&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;不能将其部署在Tomcat、Jetty等Servlet容器里，只能打成jar包执行&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;需要Spring Boot 2.0及以上的版本，才支持&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Gateway快速入门&lt;/h2&gt; &lt;p&gt;要求: 通过浏览器访问api网关,然后通过网关将请求转发到商品微服务&lt;/p&gt; &lt;h3&gt;基础版&lt;/h3&gt; &lt;p&gt;第1步:创建一个 api-gateway 的模块,导入相关依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;parent&amp;gt;     &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;     &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;     &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt; &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;api-gateway&amp;lt;/artifactId&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;!--gateway网关--&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;spring-cloud-starter-gateway&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 创建主类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu; @SpringBootApplication public class GatewayApplication {     public static void main(String[] args) {          SpringApplication.run(GatewayApplication.class, args);      } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步: 添加配置文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     gateway:       routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]         - id: product_route  # 当前路由的标识, 要求唯一           uri: http://localhost:8081 # 请求要转发到的地址           order: 1 # 路由的优先级,数字越小级别越高           predicates: # 断言(就是路由转发要满足的条件)             # 匹配路径转发             - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发           filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改             - StripPrefix=1 # 转发之前去掉1层路径  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第4步: 启动项目, 并通过网关去访问微服务&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-7.32.53.png" alt="截屏2021-02-02 下午7.32.53" title="截屏2021-02-02 下午7.32.53" /&gt;&lt;/p&gt; &lt;h3&gt;增强版&lt;/h3&gt; &lt;p&gt;第1步:加入nacos依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!--nacos客户端--&amp;gt; &amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步:在主类上添加注解&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@SpringBootApplication @EnableDiscoveryClient public class ApiGatewayApplication {  public static void main(String[] args) {    SpringApplication.run(ApiGatewayApplication.class, args);   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步:修改配置文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848     gateway:       discovery:         locator:           enabled: true       routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]         - id: product_route  # 当前路由的标识, 要求唯一           uri: http://localhost:8081 # 请求要转发到的地址           order: 1 # 路由的优先级,数字越小级别越高           predicates: # 断言(就是路由转发要满足的条件)             # 匹配路径转发             - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发           filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改             - StripPrefix=1 # 转发之前去掉1层路径  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第4步:测试&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-7.41.51.png" alt="截屏2021-02-02 下午7.41.51" title="截屏2021-02-02 下午7.41.51" /&gt;&lt;/p&gt; &lt;h3&gt;简写版&lt;/h3&gt; &lt;p&gt;第1步: 去掉关于路由的配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848     gateway:       discovery:         locator:           enabled: true # 让gateway可以发现nacos中的微服务 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 启动项目，并通过网关去访问微服&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-7.43.16.png" alt="截屏2021-02-02 下午7.43.16" title="截屏2021-02-02 下午7.43.16" /&gt;&lt;/p&gt; &lt;p&gt;这时候，就发现只要按照网关地址微服务接口的格式去访问，就可以得到成功响应。&lt;/p&gt; &lt;h2&gt;Gateway核心架构&lt;/h2&gt; &lt;h3&gt;基本概念&lt;/h3&gt; &lt;p&gt;路由(Route) 是 gateway 中最基本的组件之一，表示一个具体的路由信息载体。主要定义了下面的几个信息:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;id，路由标识符，区别于其他 Route。&lt;/li&gt; &lt;li&gt;uri，路由指向的目的地 uri，即客户端请求最终被转发到的微服务。&lt;/li&gt; &lt;li&gt;order，用于多个 Route 之间的排序，数值越小排序越靠前，匹配优先级越高。&lt;/li&gt; &lt;li&gt;predicate，断言的作用是进行条件判断，只有断言都返回真，才会真正的执行路由。&lt;/li&gt; &lt;li&gt;fifilter，过滤器用于修改请求和响应信息。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;执行流程&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-7.44.51.png" alt="截屏2021-02-02 下午7.44.51" title="截屏2021-02-02 下午7.44.51" /&gt;&lt;/p&gt; &lt;p&gt;执行流程大体如下:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Gateway Client向Gateway Server发送请求&lt;/li&gt; &lt;li&gt;请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文&lt;/li&gt; &lt;li&gt;然后网关的上下文会传递到DispatcherHandler，它负责将请求分发给RoutePredicateHandlerMapping&lt;/li&gt; &lt;li&gt;RoutePredicateHandlerMapping负责路由查找，并根据路由断言判断路由是否可用&lt;/li&gt; &lt;li&gt;如果过断言成功，由FilteringWebHandler创建过滤器链并调用&lt;/li&gt; &lt;li&gt;请求会一次经过PreFilter--微服务-PostFilter的方法，最终返回响应&lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;断言&lt;/h2&gt; &lt;p&gt;Predicate(断言, 谓词) 用于进行条件判断，只有断言都返回真，才会真正的执行路由。&lt;/p&gt; &lt;p&gt;断言就是说: 在 什么条件下 才能进行路由转发&lt;/p&gt; &lt;h3&gt;内置路由断言工厂&lt;/h3&gt; &lt;p&gt;SpringCloud Gateway包括许多内置的断言工厂，所有这些断言都与HTTP请求的不同属性匹配。具体如下:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;基于Datetime类型的断言工厂&lt;/p&gt; &lt;p&gt;此类型的断言根据时间做判断，主要有三个:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;AfterRoutePredicateFactory: 接收一个日期参数，判断请求日期是否晚于指定日期&lt;/li&gt; &lt;li&gt;BeforeRoutePredicateFactory: 接收一个日期参数，判断请求日期是否早于指定日期&lt;/li&gt; &lt;li&gt;BetweenRoutePredicateFactory: 接收两个日期参数，判断请求日期是否在指定时间段内&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;code&gt;-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于远程地址的断言工厂&lt;/p&gt; &lt;p&gt;RemoteAddrRoutePredicateFactory:接收一个IP地址段，判断请求主机地址是否在地址段中&lt;/p&gt; &lt;p&gt;&lt;code&gt;-RemoteAddr=192.168.1.1/24&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Cookie的断言工厂 CookieRoutePredicateFactory:接收两个参数，cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。 &lt;code&gt;-Cookie=chocolate, ch.&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Header的断言工厂&lt;/p&gt; &lt;p&gt;HeaderRoutePredicateFactory:接收两个参数，标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。 &lt;code&gt;-Header=X-Request-Id, \d+&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Host的断言工厂 HostRoutePredicateFactory:接收一个参数，主机名模式。判断请求的Host是否满足匹配规则。 &lt;code&gt;-Host=**.testhost.org&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Method请求方法的断言工厂 MethodRoutePredicateFactory:接收一个参数，判断请求类型是否跟指定的类型匹配。 &lt;code&gt;-Method=GET&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Path请求路径的断言工厂 PathRoutePredicateFactory:接收一个参数，判断请求的URI部分是否满足路径规则。 &lt;code&gt;-Path=/foo/{segment}&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于Query请求参数的断言工厂 QueryRoutePredicateFactory :接收两个参数，请求param和正则表达式， 判断请求参数是否具有给定名称且值与正则表达式匹配。 &lt;code&gt;-Query=baz, ba.&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;基于路由权重的断言工厂 WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发 &lt;code&gt;routes: -id: weight_route1 uri: host1 predicates: -Path=/product/** -Weight=group3, 1 -id: weight_route2 uri: host2 predicates: -Path=/product/** -Weight= group3, 9&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;内置路由断言工厂的使用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;接下来我们验证几个内置断言的使用:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848     gateway:       discovery:         locator:           enabled: true       routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]         - id: product_route  # 当前路由的标识, 要求唯一           uri: lb://service-product # 请求要转发到的地址           predicates: # 断言(就是路由转发要满足的条件)             # 匹配路径转发             - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发             - Before=2022-02-18T00:00:00.000+08:00 #限制请求时间在2022-02-18之前 \- Method=POST #限制请求方式为POST           filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改             - StripPrefix=1 # 转发之前去掉1层路径 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;自定义路由断言工厂&lt;/h3&gt; &lt;p&gt;我们来设定一个场景: 假设我们的应用仅仅让age在(min,max)之间的人来访问。&lt;/p&gt; &lt;p&gt;第1步:在配置文件中,添加一个Age的断言配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848     gateway:       discovery:         locator:           enabled: true       routes:         - id: product-route         uri: lb://service-product         predicates:           - Path=/product-serv/**           - Age=18,60 # 限制年龄只有在18到60岁之间的人能访问 filters:           - StripPrefix=1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步:自定义一个断言工厂, 实现断言方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.predicates;  import lombok.Data; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange;  import java.util.Arrays; import java.util.List; import java.util.function.Predicate; //自定义路由断言工厂 //泛型 用于接收一个配置类,配置类用于接收中配置文件中的配置 @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory&amp;lt;AgeRoutePredicateFactory.Config&amp;gt; {          public AgeRoutePredicateFactory(){             super(AgeRoutePredicateFactory.Config.class);         }         //用于从配置文件中获取参数值赋值到配置类中的属性上         @Override         public List&amp;lt;String&amp;gt; shortcutFieldOrder(){             //这里的顺序要跟配置文件中的参数顺序一致             return Arrays.asList(&amp;quot;minAge&amp;quot;,&amp;quot;maxAge&amp;quot;);         }         //断言         @Override         public Predicate&amp;lt;ServerWebExchange&amp;gt; apply( AgeRoutePredicateFactory.Config config){             return new Predicate&amp;lt;ServerWebExchange&amp;gt;(){                 @Override                 public boolean test(ServerWebExchange serverWebExchange){                     //从serverWebExchange获取传入的参数                     String ageStr=serverWebExchange.getRequest().getQueryParams().getFirst(&amp;quot;age&amp;quot;);                     if(StringUtils.isNotEmpty(ageStr)){                         int age=Integer.parseInt(ageStr);                          return age&amp;gt;config.getMinAge()&amp;amp;&amp;amp;age&amp;lt;config.getMaxAge();                     }                         return true;                     }            };         }      @Data     @NoArgsConstructor     public static class Config {         private int minAge;         private int maxAge;     }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第4步:启动测试&lt;/p&gt; &lt;pre&gt;&lt;code&gt;测试发现当age在(20,60)可以访问,其它范围不能访问  http://localhost:7000/product-serv/product/1?age=30  http://localhost:7000/product-serv/product/1?age=10 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;过滤器&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;三个知识点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;作用: 过滤器就是在请求的传递过程中,对请求和响应做一些手脚&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;生命周期: Pre Post&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;分类: 局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-8.17.20.png" alt="截屏2021-02-02 下午8.17.20" title="截屏2021-02-02 下午8.17.20" /&gt;&lt;/p&gt; &lt;p&gt;Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;GatewayFilter:应用到单个路由或者一个分组的路由上。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;GlobalFilter:应用到所有的路由上。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;局部过滤器&lt;/h3&gt; &lt;p&gt;局部过滤器是针对单个路由的过滤器。&lt;/p&gt; &lt;h4&gt;内置局部过滤器&lt;/h4&gt; &lt;p&gt;在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:&lt;/p&gt; &lt;p&gt;| 过滤器工厂 | 作用 | 参数 | | :--------: | :--: | :--: | | AddRequestHeader | 为原始请求添加Header | Header的名称及值 | | AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 | | AddResponseHeader | 为原始响应添加Header | Header的名称及值 | | DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名 称及去重策略 | | Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand的名称  | | FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header名称 | | FallbackHeaders | 为请求添加一个 preserveHostHeader=true的属 性，路由过滤器会检查该属性以 决定是否要发送原始的Host | 无 | | RequestRateLimiter | 用于对请求限流，限流算法为令牌桶 | keyResolver、 rateLimiter、 statusCode、 denyEmptyKey、 emptyKeyStatus | | RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向 的 url | | RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的 一系列Header | 默认就会启用，可以 通 过配置指定仅删除 哪些 Header | | RewriteResponseHeader | 为原始请求删除某个Header | Header的名称  | | RemoveRequestHeader | 为原始请求删除某个Header | Header的名称  | | RewritePath | 重写原始的请求路径 | 原始路径正则表达式 以 及重写后路径的正 则表达式 | | RewriteResponseHeader | 重写原始响应中的某个Header | Header名称，值的正 则表达式，重写后的 值 | | SaveSession | 在转发请求之前，强制执行WebSession::save操作 | 无 | | secureHeaders  | 为原始响应添加一系列起安全作用的响应头 | 无，支持修改这些安全  | | SetPath | 修改原始的请求路径 | 修改后的路径 | | SetResponseHeader | 修改原始响应中某个Header的 Header名称，修改后值 | Header名称，修改后值的值 | | SetStatus | 修改原始响应体的内容 | HTTP 状态码，可以是数字，也可以是字符串 | | StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 | | Retry | 针对不同的响应进行重试 | retries、statuses、methods、series | | RequestSize | 设置允许接收最大请求包的大 小。如果请求包大小超过设置的 值，则返回 413 Payload Too Large | 请求包大小，单位为字节，默认值为5M | | ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 | | ModifyResponseBody | 修改原始响应的内容 | 修改后的响应体内容 |&lt;/p&gt; &lt;p&gt;&lt;strong&gt;内置局部过滤器的使用&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848     gateway:       discovery:         locator:           enabled: true       routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]         - id: product_route  # 当前路由的标识, 要求唯一           uri: lb://service-product # 请求要转发到的地址           predicates: # 断言(就是路由转发要满足的条件)             # 匹配路径转发             - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发           filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改             - StripPrefix=1 # 转发之前去掉1层路径             - SetStatus=2000 # 修改返回状态  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;自定义局部过滤&lt;/h4&gt; &lt;p&gt;第1步:在配置文件中,添加一个Log的过滤器配置&lt;/p&gt; &lt;pre&gt;&lt;code&gt;server:   port: 7000 spring:   application:     name: api-gateway   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848       gateway:         discovery:           locator:             enabled: true # 让gateway可以发现nacos中的微服务         routes:           - id: consumer           uri: lb://consumer # lb指的是从nacos中按照名称获取微服务,并遵循负载均 衡策略           predicates:             - Path=/consumer-serv/**           filters:             - StripPrefix=1             - Log=true,false # 控制日志是否开启 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步:自定义一个过滤器工厂,实现方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.predicates;  import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;  import java.util.Arrays; import java.util.List;  //自定义局部过滤器 @Component public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory&amp;lt;LogGatewayFilterFactory.Config&amp;gt; { //构造函数      public LogGatewayFilterFactory() {         super(LogGatewayFilterFactory.Config.class);     }      //读取配置文件中的参数 赋值到 配置类中     @Override     public List&amp;lt;String&amp;gt; shortcutFieldOrder() {         return Arrays.asList(&amp;quot;consoleLog&amp;quot;, &amp;quot;cacheLog&amp;quot;);     }      //过滤器逻辑     @Override     public GatewayFilter apply(LogGatewayFilterFactory.Config config) {         return new GatewayFilter() {             @Override             public Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {                 if (config.isCacheLog()) {                     System.out.println(&amp;quot;cacheLog已经开启了....&amp;quot;);                 }                 if (config.isConsoleLog()) {                     System.out.println(&amp;quot;consoleLog已经开启了....&amp;quot;);                 }                 return chain.filter(exchange);             }         };     }      //配置类 接收配置参数     @Data     @NoArgsConstructor     public static class Config {         private boolean consoleLog;         private boolean cacheLog;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步:启动测试&lt;/p&gt; &lt;h3&gt;全局过滤器&lt;/h3&gt; &lt;p&gt;全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验，安全性验证等功 能。&lt;/p&gt; &lt;h4&gt;内置全局过滤器&lt;/h4&gt; &lt;p&gt;SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-8.47.10.png" alt="内置全局过滤器 " title="内置全局过滤器 " /&gt;:&lt;/p&gt; &lt;h4&gt;自定义全局过滤器&lt;/h4&gt; &lt;p&gt;内置的过滤器已经可以完成大部分的功能，但是对于企业开发的一些业务功能处理，还是需要我们自己编写过滤器来实现的，那么我们一起通过代码的形式自定义一个过滤器，去完成统一的权限校验。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;开发中的鉴权逻辑:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;当客户端第一次请求服务时，服务端对用户进行信息认证(登录)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;认证通过，将用户信息进行加密形成token，返回给客户端，作为登录凭证&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;以后每次请求，客户端都携带认证的token&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;服务端对token进行解密，判断是否有效。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-8.48.39.png" alt="截屏2021-02-02 下午8.48.39" title="截屏2021-02-02 下午8.48.39" /&gt;&lt;/p&gt; &lt;p&gt;如上图，对于验证用户是否已经登录鉴权的过程可以在网关统一检验。&lt;/p&gt; &lt;p&gt;检验的标准就是请求中是否携带token凭证以及token的正确性。&lt;/p&gt; &lt;p&gt;下面的我们自定义一个GlobalFilter，去校验所有请求的请求参数中是否包含“token”，如何不包含请求 参数“token”则不转发路由，否则执行正常的逻辑。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.filter;  import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;  //自定义全局过滤器需要实现GlobalFilter和Ordered接口 @Component public class AuthGlobalFilter implements GlobalFilter, Ordered {     //完成判断逻辑     @Override     public Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {         String token = exchange.getRequest().getQueryParams().getFirst(&amp;quot;token&amp;quot;);         if (StringUtils.isBlank(token)) {             System.out.println(&amp;quot;鉴权失败&amp;quot;);             exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);             return exchange.getResponse().setComplete();         }         //调用chain.filter继续向下游执行         return chain.filter(exchange);     }     //顺序,数值越小,优先级越高 @Override     @Override     public int getOrder() {         return 0;     } } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;网关限流&lt;/h2&gt; &lt;p&gt;网关是所有请求的公共入口，所以可以在网关进行限流，而且限流的方式也很多，我们本次采用前面学 过的Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-8.50.49.png" alt="截屏2021-02-02 下午8.50.49" title="截屏2021-02-02 下午8.50.49" /&gt;&lt;/p&gt; &lt;p&gt;从1.6.0版本开始，Sentinel提供了SpringCloud Gateway的适配模块，可以提供两种资源维度的限流:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;route维度:即在Spring配置文件中配置的路由条目，资源名为对应的routeId&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;第一步：导入依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.csp&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;sentinel-spring-cloud-gateway-adapter&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第一步：编写配置类&lt;/p&gt; &lt;p&gt;基于Sentinel 的Gateway限流是通过其提供的Filter来完成的，使用时只需注入对应的 SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.config;  import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;  import javax.annotation.PostConstruct; import java.util.*;  @Configuration public class GatewayConfiguration {     private final List&amp;lt;ViewResolver&amp;gt; viewResolvers;     private final     ServerCodecConfigurer serverCodecConfigurer;      public GatewayConfiguration(ObjectProvider&amp;lt;List&amp;lt;ViewResolver&amp;gt;&amp;gt; viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {         this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);         this.serverCodecConfigurer = serverCodecConfigurer;     }      // 初始化一个限流的过滤器     @Bean     @Order(Ordered.HIGHEST_PRECEDENCE)     public GlobalFilter sentinelGatewayFilter() {         return new SentinelGatewayFilter();     }      // 配置初始化的限流参数     @PostConstruct     public void initGatewayRules() {         Set&amp;lt;GatewayFlowRule&amp;gt; rules = new HashSet&amp;lt;&amp;gt;();         rules.add(new GatewayFlowRule(&amp;quot;product_route&amp;quot;) //资源名称,对应路由id .setCount(1) // 限流阈值                 .setIntervalSec(1) // 统计时间窗口，单位是秒，默认是 1 秒         );         GatewayRuleManager.loadRules(rules);     }      // 配置限流的异常处理器     @Bean     @Order(Ordered.HIGHEST_PRECEDENCE)     public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {         return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);     }      // 自定义限流异常页面     @PostConstruct     public void initBlockHandlers() {         BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {             @Override             public Mono&amp;lt;ServerResponse&amp;gt; handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {                 Map map = new HashMap&amp;lt;&amp;gt;();                 map.put(&amp;quot;code&amp;quot;, 0);                 map.put(&amp;quot;message&amp;quot;, &amp;quot;接口被限流了&amp;quot;);                 return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(map));             }         };         GatewayCallbackManager.setBlockHandler(blockRequestHandler);     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第三步：测试  在一秒钟内多次访问http://localhost:7000/product-serv/product/1?token=1就可以看到限流启作用了。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/02/2021-02-02-8.54.41.png" alt="截屏2021-02-02 下午8.54.41" title="截屏2021-02-02 下午8.54.41" /&gt;&lt;/p&gt; &lt;p&gt;第四步：自定义API分组&lt;/p&gt; &lt;p&gt;自定义API分组是一种更细粒度的限流规则定义&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.config;  import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;  import javax.annotation.PostConstruct; import java.util.*;  // 自定义API分组 限流 @Configuration public class GatewayConfiguration {     private final List&amp;lt;ViewResolver&amp;gt; viewResolvers;     private final ServerCodecConfigurer serverCodecConfigurer;      public GatewayConfiguration(ObjectProvider&amp;lt;List&amp;lt;ViewResolver&amp;gt;&amp;gt; viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {         this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);         this.serverCodecConfigurer = serverCodecConfigurer;     }      // 初始化一个限流的过滤器     @Bean     @Order(Ordered.HIGHEST_PRECEDENCE)     public GlobalFilter sentinelGatewayFilter() {         return new SentinelGatewayFilter();     }      /**      *  配置初始化的限流参数      */     @PostConstruct     public void initGatewayRules(){         Set&amp;lt;GatewayFlowRule&amp;gt; rules=new HashSet&amp;lt;&amp;gt;();         rules.add(new GatewayFlowRule(&amp;quot;product_api1&amp;quot;).setCount(1).setIntervalSec(1));         rules.add(new GatewayFlowRule(&amp;quot;product_api2&amp;quot;).setCount(1).setIntervalSec(1));         GatewayRuleManager.loadRules(rules);     }     // 配置限流的异常处理器     @Bean     @Order(Ordered.HIGHEST_PRECEDENCE)     public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {         return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);     }      // 自定义限流异常页面     @PostConstruct     public void initBlockHandlers() {         BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {             @Override             public Mono&amp;lt;ServerResponse&amp;gt; handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {                 Map map = new HashMap&amp;lt;&amp;gt;();                 map.put(&amp;quot;code&amp;quot;, 0);                 map.put(&amp;quot;message&amp;quot;, &amp;quot;接口被限流了&amp;quot;);                 return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(map));             }         };         GatewayCallbackManager.setBlockHandler(blockRequestHandler);     }       //自定义API分组     @PostConstruct     private void initCustomizedApis() {         Set&amp;lt;ApiDefinition&amp;gt; definitions = new HashSet&amp;lt;&amp;gt;();         ApiDefinition api1 = new ApiDefinition(&amp;quot;product_api1&amp;quot;)                                     .setPredicateItems(new HashSet&amp;lt;ApiPredicateItem&amp;gt;() {{                                         // 以/product-serv/product/api1 开头的请求                                          add(new ApiPathPredicateItem()                                                  .setPattern(&amp;quot;/product- serv/product/api1/**&amp;quot;)                                                  .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)                                          );                                      }});         ApiDefinition api2 = new ApiDefinition(&amp;quot;product_api2&amp;quot;)                 .setPredicateItems(new HashSet&amp;lt;ApiPredicateItem&amp;gt;() {{                     // 以/product-serv/product/api2/demo1 完成的url路径匹配                     add(new ApiPathPredicateItem().setPattern(&amp;quot;/product-serv/product/api2/demo1&amp;quot;));                 }});         definitions.add(api1);         definitions.add(api2);         GatewayApiDefinitionManager.loadApiDefinitions(definitions);     } }  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 02 Feb 2021 12:58:00 GMT</pubDate>
    </item>
    <item>
      <title>Sentinel--服务容错</title>
      <link>https://maruifu.cn/article/181</link>
      <content:encoded>&lt;h2&gt;高并发带来的问题&lt;/h2&gt; &lt;p&gt;在微服务架构中，我们将业务拆分成一个个的服务，服务与服务之间可以相互调用，但是由于网络原因 或者自身的原因，服务并不能保证服务的100%可用，如果单个服务出现问题，调用这个服务就会出现 网络延迟，此时若有大量的网络涌入，会形成任务堆积，最终导致服务瘫痪。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;接下来，我们来模拟一个高并发的场景&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt;编写java代码&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;@RestController @Slf4j public class OrderController2 {     @Autowired     private OrderService orderService;     @Autowired     private ProductService productService;      @RequestMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息&amp;quot;, pid);//调用商品微服 务,查询商品信息         Product product = productService.findByPid(pid);         log.info(&amp;quot;查询到{}号商品的信息,内容是:{}&amp;quot;, pid, JSON.toJSONString(product)); //模拟一次网络延时         try {             Thread.sleep(100);         } catch (InterruptedException e) {             e.printStackTrace();         }         //下单(创建订单)         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(pid);         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         //为了不产生太多垃圾数据,暂时不做订单保存         //orderService.createOrder(order);         log.info(&amp;quot;创建订单成功,订单信息为{}&amp;quot;, JSON.toJSONString(order));         return order;     }      @RequestMapping(&amp;quot;/order/message&amp;quot;)     public String message() {         return &amp;quot;高并发下的问题测试&amp;quot;;     } }    &lt;/code&gt;&lt;/pre&gt; &lt;ol start="2"&gt; &lt;li&gt;修改配置文件中tomcat的并发数&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;server:  #端口  port: 8091  #tomcat配置  tomcat:   #tomcat的最大并发值修改为10,默认是200   max-threads: 10 #tomcat的最大并发值修改为10,默认是200 &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;接下来使用压测工具,对请求进行压力测试&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;下载地址https://jmeter.apache.org/&lt;/p&gt; &lt;p&gt;第一步:修改配置，并启动软件&lt;/p&gt; &lt;p&gt;进入bin目录,修改jmeter.properties文件中的语言支持为language=zh_CN，然后点击jmeter.bat 启动 软件。&lt;/p&gt; &lt;p&gt;​&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.29.58.png" alt="压测工具" title="压测工具" /&gt;&lt;/p&gt; &lt;p&gt;第二步:添加线程组&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.30.30.png" alt="添加线程组" title="添加线程组" /&gt;&lt;/p&gt; &lt;p&gt;第三步:配置线程并发数&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.31.22.png" alt="配置线程并发数" title="配置线程并发数" /&gt;&lt;/p&gt; &lt;p&gt;第四步:添加Http取样&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.31.44.png" alt="添加Http取样" title="添加Http取样" /&gt;&lt;/p&gt; &lt;p&gt;第五步:配置取样，并启动测试&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.32.12.png" alt="配置取样，并启动测试" title="配置取样，并启动测试" /&gt;&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;访问message方法观察效果&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;: 此时会发现, 由于order方法囤积了大量请求, 导致message方法的访问出现了问题，这就是服务雪崩的 雏形。&lt;/p&gt; &lt;h2&gt;服务雪崩效应&lt;/h2&gt; &lt;p&gt;在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了问 题，调用这个服务就会出现线程阻塞的情况，此时若有大量的请求涌入，就会出现多条线程阻塞等待， 进而导致服务瘫痪。&lt;/p&gt; &lt;p&gt;由于服务与服务之间的依赖性，故障会传播，会对整个微服务系统造成灾难性的严重后果，这就是服务 故障的 &lt;strong&gt;雪崩效应&lt;/strong&gt; 。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.33.48.png" alt="服务雪崩效应" title="服务雪崩效应" /&gt;&lt;/p&gt; &lt;p&gt;雪崩发生的原因多种多样，有不合理的容量设计，或者是高并发下某一个方法响应变慢，亦或是某台机 器的资源耗尽。我们无法完全杜绝雪崩源头的发生，只有做好足够的容错，保证在一个服务发生问题， 不会影响到其它服务的正常运行。也就是&amp;quot;雪落而不雪崩&amp;quot;。&lt;/p&gt; &lt;h2&gt;常见容错方案&lt;/h2&gt; &lt;p&gt;要防止雪崩的扩散，我们就要做好服务的容错，容错说白了就是保护自己不被猪队友拖垮的一些措施,&lt;/p&gt; &lt;p&gt;下面介绍常见的服务容错思路和组件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;常见的容错思路&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;常见的容错思路有隔离、超时、限流、熔断、降级这几种，下面分别介绍一下。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;隔离&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;它是指将系统按照一定的原则划分为若干个服务模块，各个模块之间相对独立，无强依赖。当有故障发 生时，能将问题和影响隔离在某个模块内部，而不扩散风险，不波及其它模块，不影响整体的系统服 务。常见的隔离方式有:线程池隔离和信号量隔离.&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.35.47.png" alt="隔离" title="隔离" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;超时&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;在上游服务调用下游服务的时候，设置一个最大响应时间，如果超过这个时间，下游未作出反应，就断 开请求，释放掉线程。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.37.36.png" alt="超时" title="超时" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;限流&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要 限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.38.07.png" alt="限流" title="限流" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;熔断&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;在互联网系统中，当下游服务因访问压力过大而响应变慢或失败，上游服务为了保护系统整体的可 用性，可以暂时切断对下游服务的调用。这种牺牲局部，保全整体的措施就叫做熔断。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.39.43.png" alt="熔断**" title="熔断**" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;服务熔断一般有三种状态:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;熔断关闭状态(Closed)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;服务没有故障时，熔断器所处的状态，对调用方的调用不做任何限制&lt;/p&gt; &lt;ul&gt; &lt;li&gt;熔断开启状态(Open)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;后续对该服务接口的调用不再经过网络，直接执行本地的fallback方法&lt;/p&gt; &lt;ul&gt; &lt;li&gt;半熔断状态(Half-Open)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;尝试恢复服务调用，允许有限的流量调用该服务，并监控调用成功率。如果成功率达到预期，则说明服务已恢复，进入熔断关闭状态;如果成功率仍旧很低，则重新进入熔断关闭状态。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;降级&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;降级其实就是为服务提供一个托底方案，一旦服务无法正常调用，就使用托底方案。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-8.40.48.png" alt="降级" title="降级" /&gt;&lt;/p&gt; &lt;p&gt;常见的容错组件&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Hystrix&lt;/p&gt; &lt;p&gt;Hystrix是由Netflflix开源的一个延迟和容错库，用于隔离访问远程系统、服务或者第三方库，防止级联 失败，从而提升系统的可用性与容错性。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Resilience4J Resilicence4J一款非常轻量、简单，并且文档非常清晰、丰富的熔断工具，这也是Hystrix官方推荐的替 代产品。不仅如此，Resilicence4j还原生支持Spring Boot 1.x/2.x，而且监控也支持和prometheus等 多款主流产品进行整合。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Sentinel Sentinel 是阿里巴巴开源的一款断路器实现，本身在阿里内部已经被大规模采用，非常稳定。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;下面是三个组件在各方面的对比:&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="center"&gt;&lt;/th&gt;&lt;th&gt;Sentinel&lt;/th&gt;&lt;th&gt;Hystrix&lt;/th&gt;&lt;th align="center"&gt;resilience4j&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="center"&gt;隔离策略&lt;/td&gt;&lt;td&gt;信号量隔离(并发线程数限流)&lt;/td&gt;&lt;td&gt;线程池隔离/信号量隔离&lt;/td&gt;&lt;td align="center"&gt;信号量隔离&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;熔断降级策略&lt;/td&gt;&lt;td&gt;基于响应时间、异常比率、异常数&lt;/td&gt;&lt;td&gt;基于异常比率&lt;/td&gt;&lt;td align="center"&gt;基于异常比率、响应&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;实时统计实现&lt;/td&gt;&lt;td&gt;滑动窗口(LeapArray)&lt;/td&gt;&lt;td&gt;滑动窗口(基 于 RxJava)&lt;/td&gt;&lt;td align="center"&gt;Ring Bit Buffer&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;动态规则配置&lt;/td&gt;&lt;td&gt;支持多种数据源&lt;/td&gt;&lt;td&gt;支持多种数据&lt;/td&gt;&lt;td align="center"&gt;有限支持&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;扩展性&lt;/td&gt;&lt;td&gt;多个扩展点&lt;/td&gt;&lt;td&gt;插件的形式&lt;/td&gt;&lt;td align="center"&gt;接口的形式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;基于注解的支 持&lt;/td&gt;&lt;td&gt;支持&lt;/td&gt;&lt;td&gt;支持&lt;/td&gt;&lt;td align="center"&gt;支持&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;限流&lt;/td&gt;&lt;td&gt;基于 QPS，支持基于调用关系的限流&lt;/td&gt;&lt;td&gt;有限的支持&lt;/td&gt;&lt;td align="center"&gt;Rate Limiter&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;流量整形&lt;/td&gt;&lt;td&gt;支持预热模式、匀速器模式、预热排队模式&lt;/td&gt;&lt;td&gt;不支持&lt;/td&gt;&lt;td align="center"&gt;简单的 Rate Limiter模式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;系统自适应保护&lt;/td&gt;&lt;td&gt;支持&lt;/td&gt;&lt;td&gt;不支持&lt;/td&gt;&lt;td align="center"&gt;不支持&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;控制台&lt;/td&gt;&lt;td&gt;提供开箱即用的控制台，可配置规则、查看秒级监控、机器发现等&lt;/td&gt;&lt;td&gt;简单的监控查看&lt;/td&gt;&lt;td align="center"&gt;不提供控制台，可对接其它监控系统&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Sentinel入门&lt;/h2&gt; &lt;h3&gt;什么是Sentine&lt;/h3&gt; &lt;p&gt;Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于&lt;strong&gt;服务容错&lt;/strong&gt;的综合性解决方案。它以流量 为切入点, 从&lt;strong&gt;流量控制、熔断降级、系统负载保护&lt;/strong&gt;等多个维度来保护服务的稳定性。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Sentinel&lt;/strong&gt; &lt;strong&gt;具有以下特征&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;丰富的应用场景&lt;/strong&gt;:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;完备的实时监控&lt;/strong&gt;:Sentinel 提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒 级数据, 甚至 500 台以下规模的集群的汇总运行情况。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;广泛的开源生态&lt;/strong&gt;:Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 SpringCloud、 Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;完善的SPI扩展点&lt;/strong&gt;:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快 速地定制逻辑。例如定制规则管理、适配动态数据源等。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;Sentinel分为两个部分&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境，同时对 Dubbo/Spring Cloud 等框架也有较好的支持。&lt;/li&gt; &lt;li&gt;控制台(Dashboard)基于 Spring Boot 开发，打包后可以直接运行，不需要额外的 Tomcat 等 应用容器。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;微服务集成Sentinel&lt;/h3&gt; &lt;p&gt;为微服务集成Sentinel非常简单, 只需要加入Sentinel的依赖即可&lt;/p&gt; &lt;p&gt;1 在pom.xml中加入下面依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-sentinel&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2 编写一个Controller测试使用&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;  // @RestController @Slf4j public class OrderTestController {      @RequestMapping(&amp;quot;/order/message1&amp;quot;)     public String message1() {         return &amp;quot;message1&amp;quot;;     }      @RequestMapping(&amp;quot;/order/message2&amp;quot;)     public String message2() {         return &amp;quot;message2&amp;quot;;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装Sentinel控制台&lt;/h3&gt; &lt;p&gt;Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。&lt;/p&gt; &lt;ol&gt; &lt;li&gt;下载jar包,解压到文件夹 https://github.com/alibaba/Sentinel/releases&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;2 .启动控制台&lt;/p&gt; &lt;pre&gt;&lt;code&gt; # 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目) java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.3.jar &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;修改 shop-order ,在里面加入有关控制台的配置&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;spring:   cloud:     sentinel:       transport:         port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可          dashboard: localhost:8080 # 指定控制台服务的地址  &lt;/code&gt;&lt;/pre&gt; &lt;ol start="4"&gt; &lt;li&gt;通过浏览器访问localhost:8080 进入控制台 ( 默认用户名密码是 sentinel/sentinel )&lt;/li&gt; &lt;li&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.02.08.png" alt="控制台" title="控制台" /&gt;&lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;控制台的使用原理Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上, 即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口 调用微服务中的监控程序获取微服务的各种信息。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.03.02.png" alt="控制台" title="控制台" /&gt;&lt;/p&gt; &lt;h3&gt;实现一个接口的限流&lt;/h3&gt; &lt;p&gt;通过控制台为message1添加一个流控规则&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.03.30.png" alt="流控规则" title="流控规则" /&gt;&lt;/p&gt; &lt;p&gt;通过控制台快速频繁访问, 观察效果&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.03.59.png" alt="控制台快速频繁访问" title="控制台快速频繁访问" /&gt;&lt;/p&gt; &lt;h2&gt;Sentinel的概念和功能&lt;/h2&gt; &lt;h3&gt;基本概念&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;资源&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;资源就是Sentinel要保护的东西&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容，可以是一个服务，也可以是一个 方法，甚至可以是一段代码。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我们入门案例中的message1方法就可以认为是一个资源&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;规则&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;规则就是用来定义如何进行保护资源的&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;作用在资源之上, 定义以什么样的方式保护资源，主要包括流量控制规则、熔断降级规则以及系统保护 规则。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;我们入门案例中就是为message1资源设置了一种流控规则, 限制了进入message1的流量&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;重要功能&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.06.14.png" alt="重要功能" title="重要功能" /&gt;&lt;/p&gt; &lt;p&gt;Sentinel的主要功能就是容错，主要体现为下面这三个:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;流量控制&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;流量控制在网络传输中是一个常用的概念，它用于调整网络包的数据。任意时间到来的请求往往是随机 不可控的，而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为 一个调配器，可以根据需要把随机的请求调整成合适的形状。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;熔断降级&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;当检测到调用链路中某个资源出现不稳定的表现，例如请求响应时间长或异常比例升高的时候，则对这 个资源的调用进行限制，让请求快速失败，避免影响到其它的资源而导致级联故障&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Sentinel 对这个问题采取了两种手段:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;通过并发线程数进行限制&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Sentinel 通过限制资源并发线程的数量，来减少不稳定资源对其它资源的影响。当某个资源出现不稳定 的情况下，例如响应时间变长，对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源 上堆积到一定的数量之后，对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请 求。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;通过响应时间对资源进行降级&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;除了对并发线程数进行控制以外，Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资 源出现响应时间过长后，所有对该资源的访问都会被直接拒绝，直到过了指定的时间窗口之后才重新恢复。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Sentinel和 Hystrix的区别&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;两者的原则是一致的, 都是当一个资源出现问题时, 让其快速失败, 不要波及到其它服务 但是在限制的手段上, 确采取了完全不一样的方法:&lt;/p&gt; &lt;p&gt;Hystrix 采用的是线程池隔离的方式, 优点是做到了资源之间的隔离, 缺点是增加了线程切换 的成本。&lt;/p&gt; &lt;p&gt;Sentinel 采用的是通过并发线程的数量和响应时间来对资源做限制。&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;系统负载保护 Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候，如果还持续让请求进入 可能会导致系统崩溃，无法响应。在集群环境下，会把本应这台机器承载的流量转发到其它的机器 上去。如果这个时候其它的机器也处在一个边缘状态的时候，Sentinel 提供了对应的保护机制， 让系统的入口流量和系统的负载达到一个平衡，保证系统在能力范围之内处理最多的请求&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;总之一句话:我们需要做的事情，就是在Sentinel的资源上配置各种各样的规则，来实现各种容错的功能。&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;Sentinel规则&lt;/h2&gt; &lt;p&gt;流量控制，其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标，当达到指定的阈值时对流 量进行控制，以避免被瞬时的流量高峰冲垮，从而保障应用的高可用性。&lt;/p&gt; &lt;p&gt;第1步: 点击簇点链路，我们就可以看到访问过的接口地址，然后点击对应的流控按钮，进入流控规则配 置页面。新增流控规则界面如下&lt;/p&gt; &lt;h3&gt;流控规则&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2022/02/17/image-20220217152248149.png" alt="image-20220217152248149" title="image-20220217152248149" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;资源名&lt;/strong&gt;:唯一名称，默认是请求路径，可自定义&lt;/p&gt; &lt;p&gt;&lt;strong&gt;针对来源&lt;/strong&gt;:指定对哪个微服务进行限流，默认指default，意思是不区分来源，全部限制&lt;/p&gt; &lt;p&gt;&lt;strong&gt;阈值类型单机阈值&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候，进行限流&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;线程数:当调用该接口的线程数达到阈值的时候，进行限流&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;是否集群&lt;/strong&gt;:暂不需要集群 接下来我们以QPS为例来研究限流规则的配置。&lt;/p&gt; &lt;h4&gt;简单配置&lt;/h4&gt; &lt;p&gt;我们先做一个简单配置，设置阈值类型为QPS，单机阈值为3。即每秒请求量大于3的时候开始限流。接下来，在流控规则页面就可以看到这个配置。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.10.45.png" alt="简单配置" title="简单配置" /&gt;&lt;/p&gt; &lt;h4&gt;配置流控模式&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.11.05.png" alt="配置流控模式" title="配置流控模式" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;sentinel共有三种流控模式，分别是:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;直接(默认):接口达到限流条件时，开启限流&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;关联:当关联的资源达到限流条件时，开启限流 [适合做应用让步]&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;链路:当从某个接口过来的资源达到限流条件时，开启限流&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;下面呢分别演示三种模式:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;直接流控模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;直接流控模式是最简单的模式，当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控 模式。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;关联流控模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;关联流控模式指的是，当指定接口关联的接口达到限流条件时，开启对指定接口开启限流。第1步:配 置限流规则,将流控模式设置为关联，关联资源设置为的 /order/message2。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.12.17.png" alt="关联流控模式" title="关联流控模式" /&gt;&lt;/p&gt; &lt;p&gt;通过Jmeter软件向/order/message2连续发送请求，注意QPS一定要大于3&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.13.31.png" alt="截屏2021-01-29 下午9.13.31" title="截屏2021-01-29 下午9.13.31" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.13.48.png" alt="截屏2021-01-29 下午9.13.48" title="截屏2021-01-29 下午9.13.48" /&gt;&lt;/p&gt; &lt;p&gt;访问/order/message1,会发现已经被限流&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.14.06.png" alt="截屏2021-01-29 下午9.14.06" title="截屏2021-01-29 下午9.14.06" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;链路流控模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;链路流控模式指的是，当从某个接口过来的资源达到限流条件时，开启限流。它的功能有点类似于针对 来源配置项，区别在于:&lt;strong&gt;针对来源是针对上级微服务，而链路流控是针对上级接口，也就是说它的粒度 更细。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1.编写一个service，在里面添加一个方法message&lt;/p&gt; &lt;pre&gt;&lt;code&gt; package cn.maruifu.service.impl;   import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.stereotype.Service;  @Service public class OrderTestServiceImpl {     @SentinelResource(&amp;quot;message&amp;quot;)     public void message() {         System.out.println(&amp;quot;message&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 在Controller中声明两个方法，分别调用service中的方法message&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.service.impl.OrderTestServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;  // @RestController @Slf4j public class OrderTestController {      /*      @RequestMapping(&amp;quot;/order/message1&amp;quot;)     public String message1() {         return &amp;quot;message1&amp;quot;;     }      @RequestMapping(&amp;quot;/order/message2&amp;quot;)     public String message2() {         return &amp;quot;message2&amp;quot;;     }*/        @Autowired     private OrderTestServiceImpl orderTestServiceImpl;      @RequestMapping(&amp;quot;/order/message1&amp;quot;)     public String message1() {         orderTestServiceImpl.message();         return &amp;quot;message1&amp;quot;;     }     @RequestMapping(&amp;quot;/order/message2&amp;quot;)     public String message2() {         orderTestServiceImpl.message();         return &amp;quot;message2&amp;quot;;     }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步: 禁止收敛URL的入口 context&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;1.6.3 版本开始，Sentinel Web fifilter默认收敛所有URL的入口context，因此链路限流不生效。 1.7.0 版本开始(对应SCA的2.1.1.RELEASE)，官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数， 用于控制是否收敛context。将其配置为 false 即可根据不同的URL进行链路限流。&lt;/p&gt; &lt;p&gt;SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即 可关闭收敛我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE，无法实现链路限流。&lt;/p&gt; &lt;p&gt;目前官方还未发布SCA 2.1.2.RELEASE，所以我们只能使用2.1.1.RELEASE，需要写代码的形式实 现&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;strong&gt;(1)&lt;/strong&gt; 暂时将SpringCloud Alibaba的版本调整为2.1.1.RELEASE&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;spring-cloud-alibaba.version&amp;gt;2.1.1.RELEASE&amp;lt;/spring-cloud-alibaba.version&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;(2)&lt;/strong&gt; 配置文件中关闭sentinel的CommonFilter实例化&lt;/p&gt; &lt;pre&gt;&lt;code&gt; spring:   cloud:     sentinel:       filter:        enabled: false &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;(3)&lt;/strong&gt; 添加一个配置类，自己构建CommonFilter实例&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.config; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterContextConfig {     @Bean     public FilterRegistrationBean sentinelFilterRegistration() {         FilterRegistrationBean registration = new FilterRegistrationBean();         registration.setFilter(new CommonFilter());         registration.addUrlPatterns(&amp;quot;/*&amp;quot;);          // 入口资源关闭聚合         registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, &amp;quot;false&amp;quot;);         registration.setName(&amp;quot;sentinelFilter&amp;quot;);         registration.setOrder(1);         return registration;     } }    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;(4)&lt;/strong&gt; : 控制台配置限流规则&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.19.22.png" alt="控制台配置限流规则" title="控制台配置限流规则" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(5)&lt;/strong&gt; :分别通过 /order/message1 和 /order/message2 访问, 发现2没问题, 1的被限流了&lt;/p&gt; &lt;h4&gt;配置流控效果&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;快速失败(默认)&lt;/strong&gt;: 直接失败，抛出异常，不做任何额外的处理，是最简单的效果&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Warm Up&lt;/strong&gt;:它从开始阈值到最大QPS阈值会有一个缓冲阶段，一开始的阈值是最大QPS阈值的 1/3，然后慢慢增长，直到最大阈值，适用于将突然增大的流量转换为缓步增长的场景。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;排队等待&lt;/strong&gt;:让请求以均匀的速度通过，单机阈值为每秒通过数量，其余的排队等待; 它还会让设 置一个超时时间，当请求超过超时间时间还未处理，则会被丢弃&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;降级规则&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;降级规则就是设置当满足什么条件的时候，对服务进行降级。&lt;strong&gt;&lt;strong&gt;Sentinel&lt;/strong&gt;&lt;/strong&gt;提供了三个衡量条件:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;平均响应时间 :当资源的平均响应时间超过阈值(以 ms 为单位)之后，资源进入准降级状态。 如果接下来 1s 内持续进入 5 个请求，它们的 RT都持续超过这个阈值，那么在接下的时间窗口 (以 s 为单位)之内，就会对这个方法进行服务降级。&lt;/li&gt; &lt;li&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.20.48.png" alt="平均响应时间" title="平均响应时间" /&gt;&lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;注意 Sentinel 默认统计的 RT 上限是 4900 ms，超出此阈值的都会算作 4900 ms，若需要变更此 上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;异常比例:当资源的每秒异常总数占通过量的比值超过阈值之后，资源进入降级状态，即在接下的 时间窗口(以 s 为单位)之内，对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0,1.0]。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;第1步: 首先模拟一个异常&lt;/p&gt; &lt;pre&gt;&lt;code&gt;int i=0;  @RequestMapping(&amp;quot;/order/message2&amp;quot;)  public String message2(){     i++;     //异常比例为0.333     if(i%3==0){         throw new RuntimeException();     }     return&amp;quot;message2&amp;quot;; }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 设置异常比例为0.25&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.22.30.png" alt="置异常比例为0.25" title="置异常比例为0.25" /&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;异常数 :当资源近 1 分钟的异常数目超过阈值之后会进行服务降级。注意由于统计时间窗口是分 钟级别的，若时间窗口小于 60s，则结束熔断状态后仍可能再进入熔断状态。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.22.58.png" alt="截屏2021-01-29 下午9.22.58" title="截屏2021-01-29 下午9.22.58" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;问题:流控规则和降级规则返回的异常页面是一样的，我们怎么来区分到底是什么原因导致的 呢?&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;热点规则&lt;/h3&gt; &lt;p&gt;热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。 &lt;strong&gt;热点规则简单使用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;第1步: 编写代码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@RequestMapping(&amp;quot;/order/message3&amp;quot;)  @SentinelResource(&amp;quot;message3&amp;quot;) //注意这里必须使用这个注解标识,热点规则不生效  public String message3(String name, Integer age) {     return name + age; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 配置热点规则 &lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.25.02.png" alt="热点规则 " title="热点规则 " /&gt;&lt;/p&gt; &lt;p&gt;第3步: 分别用两个参数访问,会发现只对第一个参数限流了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.26.01.png" alt="截屏2021-01-29 下午9.26.01" title="截屏2021-01-29 下午9.26.01" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;热点规则增强使用&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;参数例外项允许对一个参数的具体值进行流控&lt;/p&gt; &lt;p&gt;编辑刚才定义的规则,增加参数例外项&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.26.34.png" alt="热点规则增强使用" title="热点规则增强使用" /&gt;&lt;/p&gt; &lt;h3&gt;授权规则&lt;/h3&gt; &lt;p&gt;很多时候，我们需要根据调用来源来判断该次请求是否允许放行，这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;若配置白名单，则只有请求来源位于白名单内时才可通过;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;若配置黑名单，则请求来源位于黑名单时不通过，其余的请求通过。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.27.10.png" alt="截屏2021-01-29 下午9.27.10" title="截屏2021-01-29 下午9.27.10" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;上面的资源名和授权类型不难理解，但是流控应用怎么填写呢?&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;其实这个位置要填写的是来源标识，Sentinel提供了 RequestOriginParser 接口来处理来源。&lt;/p&gt; &lt;p&gt;只要Sentinel保护的接口资源被访问，Sentinel就会调用 RequestOriginParser 的实现类去解析访问来源。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;第1步: 自定义来源处理规则&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Component public class RequestOriginParserDefinition implements RequestOriginParser{ @Override public String parseOrigin(HttpServletRequest request) {     String serviceName = request.getParameter(&amp;quot;serviceName&amp;quot;);     return serviceName; } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 授权规则配置&lt;/p&gt; &lt;p&gt;这个配置的意思是只有serviceName=pc不能访问(黑名单)&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.28.28.png" alt="授权规则配置" title="授权规则配置" /&gt;&lt;/p&gt; &lt;p&gt;第3步: 访问 http://localhost:8091/order/message1?serviceName=pc观察结果&lt;/p&gt; &lt;h3&gt;系统规则&lt;/h3&gt; &lt;p&gt;系统保护规则是从应用级别的入口流量进行控制，从单台机器的总体 Load、RT、入口 QPS 、CPU使用 率和线程数五个维度监控应用数据，让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统 保护规则是应用整体维度的，而不是资源维度的，并且仅对入口流量 (进入应用的流量) 生效。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值，且系统当前的并发线程数超过 系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般 是 CPU cores * 2.5。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护，单位是毫秒。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;扩展:自定义异常返回&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//异常处理页面 @Component public class ExceptionHandlerPage implements UrlBlockHandler {     //用于定义资源，并提供可选的异常处理和 fallback 配置项。其主要参数如下:     @SentinelResource      /**      *  BlockException 异常接口,包含Sentinel的五个异常      *  FlowException 限流异常      *  DegradeException 降级异常      *  ParamFlowException 参数限流异常      *  AuthorityException 授权异常      *  SystemBlockException 系统负载异常      *      */     @Override     public void blocked(HttpServletRequest request, HttpServletResponse             response, BlockException e) throws IOException { response.setContentType(&amp;quot;application/json;charset=utf-8&amp;quot;);         ResponseData data = null;         if (e instanceof FlowException) {             data = new ResponseData(-1, &amp;quot;接口被限流了...&amp;quot;);         } else if (e instanceof DegradeException) {             data = new ResponseData(-2, &amp;quot;接口被降级了...&amp;quot;);         }         response.getWriter().write(JSON.toJSONString(data));     } }     @Data      //全参构造     @AllArgsConstructor     //无参构造     @NoArgsConstructor     class ResponseData {         private int code;         private String message;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;@SentinelResource的使用&lt;/h2&gt; &lt;p&gt;在定义了资源点之后，我们可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能通过@SentinelResource来指定出现异常时的处理策略。&lt;/p&gt; &lt;p&gt;@SentinelResource 用于定义资源，并提供可选的异常处理和 fallback 配置项。其主要参数如下:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="center"&gt;属性&lt;/th&gt;&lt;th align="center"&gt;作用&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="center"&gt;value&lt;/td&gt;&lt;td align="center"&gt;资源名称&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;entryType&lt;/td&gt;&lt;td align="center"&gt;entry类型，标记流量的方向，取值IN/OUT，默认是OUT&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;blockHandler&lt;/td&gt;&lt;td align="center"&gt;处理BlockException的函数名称,函数要求:1. 必须是 public2. 返回类型 参数与原方法一致3. 默认需和原方法在同一个类中。若希望使用其他类 的函数，可配置blockHandlerClass ，并指定blockHandlerClass里面的 方法。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;blockHandlerClass&lt;/td&gt;&lt;td align="center"&gt;存放blockHandler的类,对应的处理函数必须static修饰。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;fallback&lt;/td&gt;&lt;td align="center"&gt;用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所 有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行 处理。函数要求:1. 返回类型与原方法一致2. 参数类型需要和原方法相 匹配3. 默认需和原方法在同一个类中。若希望使用其他类的函数，可配 置fallbackClass ，并指定fallbackClass里面的方法。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;fallbackClass&lt;/td&gt;&lt;td align="center"&gt;存放fallback的类。对应的处理函数必须static修饰&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;defaultFallback&lt;/td&gt;&lt;td align="center"&gt;用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进 行处理。若同时配置了 fallback 和 defaultFallback，以fallback为准。函 数要求:1. 返回类型与原方法一致2. 方法参数列表为空，或者有一个 Throwable 类型的参数。3. 默认需要和原方法在同一个类中。若希望使 用其他类的函数，可配置fallbackClass ，并指定 fallbackClass 里面的方 法。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;exceptionsToIgnore&lt;/td&gt;&lt;td align="center"&gt;指定排除掉哪些异常。排除的异常不会计入异常统计，也不会进入 fallback逻辑，而是原样抛出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;exceptionsToTrace&lt;/td&gt;&lt;td align="center"&gt;需要trace的异常&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;strong&gt;定义限流和降级后的处理方法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;方式一:直接将限流和降级方法定义在方法中&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Service @Slf4j public class OrderServiceImpl3 {     int i = 0;     @SentinelResource(value = &amp;quot;message&amp;quot;,  /*指定发生BlockException时进入的方法*/     blockHandler = &amp;quot;blockHandler&amp;quot;, /* 指定发生Throwable时进入的方法) */      fallback = &amp;quot;fallback&amp;quot;     public String message(){         i++;         if(i % 3 == 0){           throw new RuntimeException();         }          return&amp;quot;message&amp;quot;;      }                          //BlockException时进入的方法      public String blockHandler(BlockException ex){         log.error(&amp;quot;{}&amp;quot;, ex);         return&amp;quot;接口被限流或者降级了...&amp;quot;;     }      //Throwable时进入的方法     public String fallback(Throwable throwable) {         log.error(&amp;quot;{}&amp;quot;, throwable);         return &amp;quot;接口发生异常了...&amp;quot;;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;方式二: 将限流和降级方法外置到单独的类中&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Service @Slf4j public class OrderServiceImpl3 {    int i = 0;    @SentinelResource(  value = &amp;quot;message&amp;quot;, blockHandlerClass = OrderServiceImpl3BlockHandlerClass.class, blockHandler = &amp;quot;blockHandler&amp;quot;, fallbackClass = OrderServiceImpl3FallbackClass.class, fallback = &amp;quot;fallback&amp;quot;  )    public String message() {        i++;        if (i % 3 == 0) {            throw new RuntimeException();        }        return &amp;quot;message4&amp;quot;;    } } @Slf4j public class OrderServiceImpl3BlockHandlerClass { //注意这里必须使用static修饰方法    public static String blockHandler(BlockException ex) { 1 编写处理类        log.error(&amp;quot;{}&amp;quot;, ex);        return &amp;quot;接口被限流或者降级了...&amp;quot;;    } } @Slf4j public class OrderServiceImpl3FallbackClass { //注意这里必须使用static修饰方法    public static String fallback(Throwable throwable) { log.error(&amp;quot;{}&amp;quot;, throwable);        return &amp;quot;接口发生异常了...&amp;quot;;    } }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Sentinel规则持久化&lt;/h2&gt; &lt;p&gt;通过前面的讲解，我们已经知道，可以通过Dashboard来为每个Sentinel客户端设置各种各样的规则，&lt;/p&gt; &lt;p&gt;但是这里有一个问题，就是这些规则默认是存放在内存中，极不稳定，所以需要将其持久化。 本地文件数据源会定时轮询文件的变更，读取规则。这样我们既可以在应用本地直接修改文件来更新规则，也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例，推送过程如下图所示:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.42.57.png" alt="截屏2021-01-29 下午9.42.57" title="截屏2021-01-29 下午9.42.57" /&gt;&lt;/p&gt; &lt;p&gt;首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中，接着注册的写数据源会将新的规则 保存到本地的文件中。&lt;/p&gt; &lt;p&gt;编写处理类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.config;  import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler; import com.alibaba.csp.sentinel.datasource.*; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.springframework.beans.factory.annotation.Value;  import java.io.File; import java.io.IOException; import java.util.List;  //规则持久化 public class FilePersistence implements InitFunc {     @Value(&amp;quot;spring.application:name&amp;quot;)     private String appcationName;      @Override     public void init() throws Exception {         String ruleDir = System.getProperty(&amp;quot;user.home&amp;quot;) + &amp;quot;/sentinel-rules/&amp;quot; + appcationName;         String flowRulePath = ruleDir + &amp;quot;/flow-rule.json&amp;quot;;         String degradeRulePath = ruleDir + &amp;quot;/degrade-rule.json&amp;quot;;         String systemRulePath = ruleDir + &amp;quot;/system-rule.json&amp;quot;;         String authorityRulePath = ruleDir + &amp;quot;/authority-rule.json&amp;quot;;         String paramFlowRulePath = ruleDir + &amp;quot;/param-flow-rule.json&amp;quot;;          this.mkdirIfNotExits(ruleDir);         this.createFileIfNotExits(flowRulePath);         this.createFileIfNotExits(degradeRulePath);         this.createFileIfNotExits(systemRulePath);         this.createFileIfNotExits(authorityRulePath);         this.createFileIfNotExits(paramFlowRulePath);         // 流控规则         ReadableDataSource&amp;lt;String, List&amp;lt;FlowRule&amp;gt;&amp;gt; flowRuleRDS = new FileRefreshableDataSource&amp;lt;&amp;gt;         (flowRulePath, flowRuleListParser         );         FlowRuleManager.register2Property(flowRuleRDS.getProperty());         WritableDataSource&amp;lt;List&amp;lt;FlowRule&amp;gt;&amp;gt; flowRuleWDS = new FileWritableDataSource&amp;lt;&amp;gt;(flowRulePath, this::encodeJson);         WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);         // 降级规则         ReadableDataSource&amp;lt;String, List&amp;lt;DegradeRule&amp;gt;&amp;gt; degradeRuleRDS = new FileRefreshableDataSource&amp;lt;&amp;gt;(degradeRulePath, degradeRuleListParser);         DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());         WritableDataSource&amp;lt;List&amp;lt;DegradeRule&amp;gt;&amp;gt; degradeRuleWDS = new FileWritableDataSource&amp;lt;&amp;gt;(degradeRulePath, this::encodeJson);         WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);         // 系统规则         ReadableDataSource&amp;lt;String, List&amp;lt;SystemRule&amp;gt;&amp;gt; systemRuleRDS = new FileRefreshableDataSource&amp;lt;&amp;gt;(systemRulePath, systemRuleListParser);         SystemRuleManager.register2Property(systemRuleRDS.getProperty());         WritableDataSource&amp;lt;List&amp;lt;SystemRule&amp;gt;&amp;gt; systemRuleWDS = new FileWritableDataSource&amp;lt;&amp;gt;(systemRulePath, this::encodeJson);         WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);         // 授权规则         ReadableDataSource&amp;lt;String, List&amp;lt;AuthorityRule&amp;gt;&amp;gt; authorityRuleRDS = new FileRefreshableDataSource&amp;lt;&amp;gt;(authorityRulePath, authorityRuleListParser);         AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());         WritableDataSource&amp;lt;List&amp;lt;AuthorityRule&amp;gt;&amp;gt; authorityRuleWDS = new FileWritableDataSource&amp;lt;&amp;gt;(authorityRulePath, this::encodeJson);         WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);         // 热点参数规则         ReadableDataSource&amp;lt;String, List&amp;lt;ParamFlowRule&amp;gt;&amp;gt; paramFlowRuleRDS = new FileRefreshableDataSource&amp;lt;&amp;gt;( paramFlowRulePath,paramFlowRuleListParser);         ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());         WritableDataSource&amp;lt;List&amp;lt;ParamFlowRule&amp;gt;&amp;gt; paramFlowRuleWDS = new FileWritableDataSource&amp;lt;&amp;gt;( paramFlowRulePath, this::encodeJson );         ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);     }      private Converter&amp;lt;String, List&amp;lt;FlowRule&amp;gt;&amp;gt; flowRuleListParser = source -&amp;gt; JSON.parseObject( source,  new TypeReference&amp;lt;List&amp;lt;FlowRule&amp;gt;&amp;gt;() { });      private Converter&amp;lt;String, List&amp;lt;DegradeRule&amp;gt;&amp;gt; degradeRuleListParser = source -&amp;gt; JSON.parseObject(  source, new TypeReference&amp;lt;List&amp;lt;DegradeRule&amp;gt;&amp;gt;() {  });      private Converter&amp;lt;String, List&amp;lt;SystemRule&amp;gt;&amp;gt; systemRuleListParser = source -&amp;gt; JSON.parseObject(source, new TypeReference&amp;lt;List&amp;lt;SystemRule&amp;gt;&amp;gt;() {});      private Converter&amp;lt;String, List&amp;lt;AuthorityRule&amp;gt;&amp;gt; authorityRuleListParser = source -&amp;gt; JSON.parseObject(source,new TypeReference&amp;lt;List&amp;lt;AuthorityRule&amp;gt;&amp;gt;() {} );      private Converter&amp;lt;String, List&amp;lt;ParamFlowRule&amp;gt;&amp;gt; paramFlowRuleListParser = source -&amp;gt; JSON.parseObject(source,new TypeReference&amp;lt;List&amp;lt;ParamFlowRule&amp;gt;&amp;gt;() { } );      private void mkdirIfNotExits(String filePath) throws IOException {         File file = new File(filePath);         if (!file.exists()) {             file.mkdirs();         }     }      private void createFileIfNotExits(String filePath) throws IOException {         File file = new File(filePath);         if (!file.exists()) {             file.createNewFile();         }     }      private &amp;lt;T&amp;gt; String encodeJson(T t) {         return JSON.toJSONString(t);     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;添加配置 在resources下创建配置目录 META-INF/services ,然后添加文件&lt;code&gt;com.alibaba.csp.sentinel.init.InitFunc&lt;/code&gt;&lt;/p&gt; &lt;p&gt;在文件中添加配置类的全路径&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cn.maruifu.config.FilePersistence &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Feign整合Sentinel&lt;/h2&gt; &lt;p&gt;第1步: 引入sentinel的依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;!--sentinel客户端--&amp;gt; &amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-sentinel&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步: 在配置文件中开启Feign对Sentinel的支持&lt;/p&gt; &lt;pre&gt;&lt;code&gt;feign:  sentinel:   enabled: true &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第3步: 创建容错类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//容错类要求必须实现被容错的接口,并为每个方法实现容错方案 package cn.maruifu.service.api;  import cn.maruifu.vo.Product; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;  //容错类要求必须实现被容错的接口,并为每个方法实现容错方案 @Component @Slf4j public class ProductApiServiceFallBack implements ProductApiService {     @Override     public Product findByPid(Integer pid) {         Product product = new Product();         product.setPid(-1);         return product;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第4步: 为被容器的接口指定容错类&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.service.api;  import cn.maruifu.vo.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; //value用于指定调用nacos下哪个微服务 //fallback用于指定容错类 @FeignClient(value = &amp;quot;service-product&amp;quot;, fallback = ProductApiServiceFallBack.class) //声明调用的提供者的name public interface ProductApiService {     //指定调用提供者的哪个方法 //@FeignClient+@GetMapping 就是一个完整的请求路径 http://service- product/product/{pid}     @GetMapping(value = &amp;quot;/product/{pid}&amp;quot;)     Product findByPid(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid); }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第5步: 修改controller&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.service.OrderService; import cn.maruifu.service.api.ProductApiService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  import java.util.List; import java.util.Random;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;      @Autowired     private DiscoveryClient discoveryClient;         @Autowired     private ProductApiService productApiService;      //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);          // 方法一 通过IP调用         //通过restTemplate调用商品微服务         //Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);          // 方法二 从nacos中获取服务地址         //ServiceInstance serviceInstance = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;).get(0);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; +serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          // 方法三 从nacos中获取服务地址 自定义规则实现随机挑选服务         //List&amp;lt;ServiceInstance&amp;gt; instances = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;);         //int index = new Random().nextInt(instances.size());         //ServiceInstance serviceInstance = instances.get(index);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; + serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          // 方法四  基于Ribbon实现负载均衡         //直接使用微服务名字， 从nacos中获取服务地址         //String url = &amp;quot;service-product&amp;quot;;         //Product product = restTemplate.getForObject( &amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          // 方法五 通过fegin调用商品微服务          //  调用商品微服务,查询商品信息         log.info(&amp;quot;接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息&amp;quot;, pid);          Product product = productApiService.findByPid(pid);          if (product.getPid() == -1) {             Order order = new Order();             order.setPname(&amp;quot;下单失败&amp;quot;);             return order;         }            log.info(&amp;quot;查询到{}号商品的信息,内容是:{}&amp;quot;, pid, JSON.toJSONString(product)); //模拟一次网络延时         try {             Thread.sleep(1000);         } catch (InterruptedException e) {             e.printStackTrace();         }         //下单(创建订单)         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);           //为了不产生太多垃圾数据,暂时不做订单保存         //orderService.save(order);         log.info(&amp;quot;创建订单成功,订单信息为{}&amp;quot;, JSON.toJSONString(order));          return order;     }       @RequestMapping(&amp;quot;/order/message&amp;quot;)     public String message() {         return &amp;quot;高并发下的问题测试&amp;quot;;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第6步: 停止所有 shop-product 服务,重启 shop-order 服务,访问请求,观察容错效果&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/29/2021-01-29-9.53.43.png" alt="截屏2021-01-29 下午9.53.43" title="截屏2021-01-29 下午9.53.43" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;扩展&lt;/strong&gt;: 如果想在容错类中拿到具体的错误,可以使用下面的方式&lt;/p&gt; &lt;pre&gt;&lt;code&gt; package cn.maruifu.service.api;  import cn.maruifu.vo.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; //value用于指定调用nacos下哪个微服务 //fallback用于指定容错类 //@FeignClient(value = &amp;quot;service-product&amp;quot;, fallback = ProductApiServiceFallBack.class)  @FeignClient(   value = &amp;quot;service-product&amp;quot;, fallbackFactory = ProductApiServiceFallBackFactory.class) //声明调用的提供者的name public interface ProductApiService {     //指定调用提供者的哪个方法 //@FeignClient+@GetMapping 就是一个完整的请求路径 http://service- product/product/{pid}     @GetMapping(value = &amp;quot;/product/{pid}&amp;quot;)     Product findByPid(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid); }     package cn.maruifu.service.api; import cn.maruifu.vo.Product; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class ProductApiServiceFallBackFactory implements FallbackFactory&amp;lt;ProductApiService&amp;gt; {     @Override     public ProductApiService create(Throwable throwable) {         return new ProductApiService() {             @Override             public Product findByPid(Integer pid) {                 throwable.printStackTrace();                 Product product = new Product();                 product.setPid(-1);                 return product;             }         };     } }      &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;注意: fallback和fallbackFactory只能使用其中一种方式&lt;/strong&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 29 Jan 2021 13:57:00 GMT</pubDate>
    </item>
    <item>
      <title>Nacos Discovery--服务治理</title>
      <link>https://maruifu.cn/article/180</link>
      <content:encoded>&lt;h2&gt;服务治理介绍&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;先来思考一个问题&lt;/strong&gt;？&lt;/p&gt; &lt;p&gt;通过上一章的操作，我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址(ip，端 口)等硬编码到了代码中，这种做法存在许多问题:&lt;/p&gt; &lt;p&gt;一旦服务提供者地址变化，就需要手工修改代码 一旦是多个服务提供者，无法实现负载均衡功能 一旦服务变得越来越多，人工维护调用关系困难&lt;/p&gt; &lt;p&gt;那么应该怎么解决呢， 这时候就需要通过注册中心动态的实现&lt;strong&gt;服务治理&lt;/strong&gt;。&lt;/p&gt; &lt;h3&gt;&lt;strong&gt;什么是服务治理&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的&lt;strong&gt;自动化注册与发现&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;**服务注册:**在服务治理框架中，都会构建一个注册中心，每个服务单元向注册中心登记自己提供服 务的详细信息。并在注册中心形成一张服务的清单，服务注册中心需要以心跳的方式去监测清单中 的服务是否可用，如果不可用，需要在服务清单中剔除不可用的服务。&lt;/p&gt; &lt;p&gt;**服务发现:**服务调用方向服务注册中心咨询服务，并获取所有服务的实例清单，实现对具体服务实例的访问。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127213529134.png" alt="服务治理" title="服务治理" /&gt;&lt;/p&gt; &lt;p&gt;通过上面的调用图会发现，除了微服务，还有一个组件是&lt;strong&gt;服务注册中心&lt;/strong&gt;，它是微服务架构非常重要的一 个组件，在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;服务发现: 服务注册:保存服务提供者和服务调用者的信息&lt;/p&gt; &lt;p&gt;服务订阅:服务调用者订阅服务提供者的信息，注册中心向订阅者推送提供者的信息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;服务配置:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;配置订阅:服务提供者和服务调用者订阅微服务相关的配置&lt;/li&gt; &lt;li&gt;配置下发:主动将配置推送给服务提供者和服务调用者&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;服务健康检测 检测服务提供者的健康情况，如果发现异常，执行服务剔除&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;&lt;strong&gt;常见的注册中心&lt;/strong&gt;&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Zookeeper&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;zookeeper是一个分布式服务框架，是Apache Hadoop 的一个子项目，它主要是用来解决分布式 应用中经常遇到的一些数据管理问题，如:统一命名服务、状态同步服务、集群管理、分布式应用 配置项的管理等。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Eureka&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Eureka是Springcloud Netflix中的重要组件，主要作用就是做服务注册和发现。但是现在已经闭 源&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Consul&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Consul是基于GO语言开发的开源工具，主要面向分布式，服务化的系统提供服务注册、服务发现 和配置管理的功能。Consul的功能都很实用，其中包括:服务注册/发现、健康检查、Key/Value 存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件，所以 安装和部署都非常简单，只需要从官网下载后，在执行对应的启动脚本即可。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Nacos&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。他是SpringCloud Alibaba 组件之一，负责服务注册发现和服务配置，可以这样认为 nacos=eureka+config。&lt;/p&gt; &lt;h2&gt;nacos简介&lt;/h2&gt; &lt;p&gt;Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集，帮助您快速实现&lt;/p&gt; &lt;p&gt;动态服务发现、服务配置、服务元数据及流量管理。 从上面的介绍就可以看出，&lt;strong&gt;nacos的作用就是一个注册中心&lt;/strong&gt;，用来管理注册上来的各个微服务。&lt;/p&gt; &lt;h2&gt;nacos实战入门&lt;/h2&gt; &lt;p&gt;接下来，我们就在现有的环境中加入nacos，并将我们的两个微服务注册上去。&lt;/p&gt; &lt;h3&gt;搭建nacos环境&lt;/h3&gt; &lt;h4&gt;安装nacos&lt;/h4&gt; &lt;p&gt;从 Github 上下载源码方式&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U   ls -al distribution/target/  // change the $version to your actual path cd distribution/target/nacos-server-$version/nacos/bin &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;下载编译后压缩包方式&lt;/p&gt; &lt;p&gt;您可以从 &lt;a href="https://github.com/alibaba/nacos/releases" target="_blank"&gt;最新稳定版本&lt;/a&gt; 下载 &lt;code&gt;nacos-server-$version.zip&lt;/code&gt; 包。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;  unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz   cd nacos/bin &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;启动nacos&lt;/h4&gt; &lt;p&gt;Linux/Unix/Mac启动命令(standalone代表着单机模式运行，非集群模式):&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sh startup.sh -m standalone &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果您使用的是ubuntu系统，或者运行脚本报错提示[[符号找不到，可尝试如下运行：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;bash startup.sh -m standalone &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Windows启动命令(standalone代表着单机模式运行，非集群模式):&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cmd startup.cmd -m standalone &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;启动失败看一下自己是不是64位 1.8+JDK&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;访问nacos&lt;/h4&gt; &lt;p&gt;打开浏览器输入http://localhost:8848/nacos，即可访问服务， 默认密码是nacos/nacos&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127214154181.png" alt=" 访问nacos" title=" 访问nacos" /&gt;&lt;/p&gt; &lt;h3&gt;将商品微服务注册到nacos&lt;/h3&gt; &lt;p&gt;接下来开始修改 shop-product 模块的代码， 将其注册到nacos服务上&lt;/p&gt; &lt;p&gt;在pom.xml中添加nacos的依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;!--nacos客户端--&amp;gt; &amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在主类上添加**@EnableDiscoveryClient**注解&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@SpringBootApplication @EnableDiscoveryClient public class ProductApplication &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在application.yml中添加nacos服务的地址&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:     cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;启动服务， 观察nacos的控制面板中是否有注册上来的商品微服务&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127214557772.png" alt="商品微服务" title="商品微服务" /&gt;&lt;/p&gt; &lt;h3&gt;将订单微服务注册到nacos&lt;/h3&gt; &lt;p&gt;接下来开始修改 shop_order 模块的代码， 将其注册到nacos服务上&lt;/p&gt; &lt;p&gt;在pom.xml中添加nacos的依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  &amp;lt;!--nacos客户端--&amp;gt; &amp;lt;dependency&amp;gt;  &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在主类上添加**@EnableDiscoveryClient**注解&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;   @SpringBootApplication @EnableDiscoveryClient public class OrderApplication &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在application.yml中添加nacos服务的地址&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring:   cloud:     nacos:       discovery:         server-addr: 127.0.0.1:8848 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改OrderController， 实现微服务调用&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;      @Autowired     private DiscoveryClient discoveryClient;       //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);          // 之前调用         //通过restTemplate调用商品微服务         //Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址         ServiceInstance serviceInstance = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;).get(0);         String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; +serviceInstance.getPort();         log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);                  log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot;+ JSON.toJSONString(product));         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         return order;     } }   &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;DiscoveryClient是专门负责服务注册和发现的，我们可以通过它获取到注册到注册中心的所有服务&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;启动服务， 观察nacos的控制面板中是否有注册上来的订单微服务，然后通过访问消费者服务验证调 用是否成功&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127214959915.png" alt="订单微服务" title="订单微服务" /&gt;&lt;/p&gt; &lt;h2&gt;实现服务调用的负载均衡&lt;/h2&gt; &lt;h3&gt;什么是负载均衡&lt;/h3&gt; &lt;p&gt;通俗的讲， 负载均衡就是将负载(工作任务，访问请求)进行分摊到多个操作单元(服务器,组件)上 进行执行。&lt;/p&gt; &lt;p&gt;根据负载均衡发生位置的不同,一般分为&lt;strong&gt;服务端负载均衡&lt;/strong&gt;和&lt;strong&gt;客户端负载均衡&lt;/strong&gt;。 服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡&lt;/p&gt; &lt;p&gt;而客户端负载均衡指的是发生在服务请求的一方，也就是在发送请求之前已经选好了由哪个实例处理请求。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127215055557.png" alt="负载均衡" title="负载均衡" /&gt;&lt;/p&gt; &lt;p&gt;我们在微服务调用关系中一般会选择客户端负载均衡，也就是在服务调用的一方来决定服务由哪个提供 者执行。&lt;/p&gt; &lt;h3&gt;自定义实现负载均衡&lt;/h3&gt; &lt;p&gt;1通过idea再启动一个 shop-product 微服务，点击Copy Configurations 设置其端口为8082&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127215133849.png" alt="image-20210127215133849" title="image-20210127215133849" /&gt;&lt;/p&gt; &lt;p&gt;2通过nacos查看微服务的启动情况&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127215154924.png" alt="image-20210127215154924" title="image-20210127215154924" /&gt;&lt;/p&gt; &lt;p&gt;3修改 shop-order 的代码，实现负载均衡&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  import java.util.List; import java.util.Random;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;      @Autowired     private DiscoveryClient discoveryClient;       //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);          // 之前调用         //通过restTemplate调用商品微服务         //Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址         //ServiceInstance serviceInstance = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;).get(0);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; +serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址 自定义规则实现随机挑选服务         List&amp;lt;ServiceInstance&amp;gt; instances = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;);         int index = new Random().nextInt(instances.size());         ServiceInstance serviceInstance = instances.get(index);         String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; + serviceInstance.getPort();         log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);           log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot;+ JSON.toJSONString(product));         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         return order;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4启动两个服务提供者和一个服务消费者，多访问几次消费者测试效果&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127215418236.png" alt="image-20210127215418236" title="image-20210127215418236" /&gt;&lt;/p&gt; &lt;h3&gt;基于Ribbon实现负载均衡&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;Ribbon&lt;/strong&gt; 是 Spring Cloud 的一个组件， 它可以让我们使用一个注解就能轻松的搞定负载均衡&lt;/p&gt; &lt;p&gt;第1步:在RestTemplate 的生成方法上添加@LoadBalanced注解&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@Bean @LoadBalanced public RestTemplate restTemplate() {   return new RestTemplate(); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第2步:修改服务调用的方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  import java.util.List; import java.util.Random;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;      @Autowired     private DiscoveryClient discoveryClient;       //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);          // 之前调用         //通过restTemplate调用商品微服务         //Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址         //ServiceInstance serviceInstance = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;).get(0);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; +serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址 自定义规则实现随机挑选服务         //List&amp;lt;ServiceInstance&amp;gt; instances = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;);         //int index = new Random().nextInt(instances.size());         //ServiceInstance serviceInstance = instances.get(index);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; + serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          // 基于Ribbon实现负载均衡         //直接使用微服务名字， 从nacos中获取服务地址         String url = &amp;quot;service-product&amp;quot;;         Product product = restTemplate.getForObject( &amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot;+ JSON.toJSONString(product));         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         return order;     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;Ribbon支持的负载均衡策略&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule ,&lt;/p&gt; &lt;p&gt;具体的负载策略如下图所示:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127215929118.png" alt="负载策略" title="负载策略" /&gt;&lt;/p&gt; &lt;p&gt;我们可以通过修改配置来调整Ribbon的负载均衡策略，具体代码如下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;service-product: # 调用的提供者的名称  ribbon:   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;基于Feign实现服务调用&lt;/h2&gt; &lt;h3&gt;什么是Feign&lt;/h3&gt; &lt;p&gt;Feign是Spring Cloud提供的一个声明式的伪Http客户端， 它使得调用远程服务就像调用本地服务一样 简单， 只需要创建一个接口并添加一个注解即可。&lt;/p&gt; &lt;p&gt;Nacos很好的兼容了Feign， Feign默认集成了 Ribbon， 所以在Nacos下使用Fegin默认就实现了负载均 衡的效果。&lt;/p&gt; &lt;h3&gt;Feign的使用&lt;/h3&gt; &lt;h4&gt;加入Fegin的依赖&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!--fegin组件--&amp;gt; &amp;lt;dependency&amp;gt;   &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;   &amp;lt;artifactId&amp;gt;spring-cloud-starter-openfeign&amp;lt;/artifactId&amp;gt;  &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;在主类上添加Fegin的注解&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;@SpringBootApplication  @EnableDiscoveryClient  @EnableFeignClients //开启Fegin  public class OrderApplication {} &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;创建一个service&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.api;  import cn.maruifu.vo.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;  @FeignClient(&amp;quot;service-product&amp;quot;) //声明调用的提供者的name public interface ProductApiService {     //指定调用提供者的哪个方法     //@FeignClient+@GetMapping 就是一个完整的请求路径 http://service- product/product/{pid}     @GetMapping(value = &amp;quot;/product/{pid}&amp;quot;)     Product findByPid(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid); } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;修改controller代码&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu.controller;  import cn.maruifu.api.ProductApiService; import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  import java.util.List; import java.util.Random;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;      @Autowired     private DiscoveryClient discoveryClient;     @Autowired     private ProductApiService productApiService;      //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);          // 之前调用         //通过restTemplate调用商品微服务         //Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址         //ServiceInstance serviceInstance = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;).get(0);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; +serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          //从nacos中获取服务地址 自定义规则实现随机挑选服务         //List&amp;lt;ServiceInstance&amp;gt; instances = discoveryClient.getInstances(&amp;quot;service-product&amp;quot;);         //int index = new Random().nextInt(instances.size());         //ServiceInstance serviceInstance = instances.get(index);         //String url = serviceInstance.getHost() + &amp;quot;:&amp;quot; + serviceInstance.getPort();         //log.info(&amp;quot;&amp;gt;&amp;gt;从nacos中获取到的微服务地址为:&amp;quot; + url);         //Product product = restTemplate.getForObject(&amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          // 基于Ribbon实现负载均衡         //直接使用微服务名字， 从nacos中获取服务地址         // String url = &amp;quot;service-product&amp;quot;;         // Product product = restTemplate.getForObject( &amp;quot;http://&amp;quot; + url + &amp;quot;/product/&amp;quot; + pid, Product.class);          //通过fegin调用商品微服务         Product product = productApiService.findByPid(pid);           log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot;+ JSON.toJSONString(product));         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         return order;     } }   &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;重启order微服务,查看效果&lt;/h4&gt;</content:encoded>
      <pubDate>Wed, 27 Jan 2021 14:06:00 GMT</pubDate>
    </item>
    <item>
      <title>微服务环境搭建</title>
      <link>https://maruifu.cn/article/179</link>
      <content:encoded>&lt;p&gt;我们本次是使用的电商项目中的商品、订单、用户为案例进行讲解.&lt;/p&gt; &lt;h2&gt;案例准备&lt;/h2&gt; &lt;h3&gt;技术选型&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;maven:&lt;/strong&gt; 3.3.9&lt;/p&gt; &lt;p&gt;&lt;strong&gt;数据库:&lt;/strong&gt; MySQL 5.7&lt;/p&gt; &lt;p&gt;&lt;strong&gt;持久层:&lt;/strong&gt; SpingData Jpa&lt;/p&gt; &lt;p&gt;&lt;strong&gt;其他:&lt;/strong&gt; SpringCloud Alibaba 技术栈&lt;/p&gt; &lt;h3&gt;模块设计&lt;/h3&gt; &lt;p&gt;springcloud-alibaba 父工程&lt;/p&gt; &lt;p&gt;shop-common 公共模块【实体类】&lt;/p&gt; &lt;p&gt;shop-user 用户微服务 【端口: 8071】&lt;/p&gt; &lt;p&gt;shop-product 商品微服务 【端口: 8081】&lt;/p&gt; &lt;p&gt;shop-order 订单微服务 【端口: 8091】&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127203623839.png" alt="模块设计" title="模块设计" /&gt;&lt;/p&gt; &lt;h3&gt;微服务调用&lt;/h3&gt; &lt;p&gt;在微服务架构中，最常见的场景就是微服务之间的相互调用。我们以电商系统中常见的&lt;strong&gt;用户下单&lt;/strong&gt;为例来 演示微服务的调用:客户向订单微服务发起一个下单的请求，在进行保存订单之前需要调用商品微服务 查询商品的信息。&lt;/p&gt; &lt;p&gt;我们一般把服务的主动调用方称为&lt;strong&gt;服务消费者&lt;/strong&gt;，把服务的被调用方称为&lt;strong&gt;服务提供者&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127203651412.png" alt="微服务调用" title="微服务调用" /&gt;&lt;/p&gt; &lt;p&gt;在这种场景下，订单微服务就是一个服务消费者， 商品微服务就是一个服务提供者。&lt;/p&gt; &lt;h2&gt;创建数据库&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;DROP DATABASE IF EXISTS `shop`; CREATE DATABASE  `shop` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; USE `shop`; CREATE TABLE `shop_order` ( `oid` int NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(255) DEFAULT NULL COMMENT '用户名', `uid` int DEFAULT NULL COMMENT '用户id', `pid` int DEFAULT NULL COMMENT '商品ID', `pname` varchar(255) DEFAULT NULL COMMENT '商品名称', `pprice` decimal(10,2) DEFAULT NULL COMMENT '商品价格', `number` int DEFAULT NULL COMMENT '数量', PRIMARY KEY (`oid`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;  CREATE TABLE `shop_product` ( `pid` int NOT NULL AUTO_INCREMENT COMMENT '主键', `pname` varchar(255) DEFAULT NULL COMMENT '商品名称', `pprice` decimal(10,2) DEFAULT NULL COMMENT '商品价格', `stock` int DEFAULT NULL COMMENT '库存', PRIMARY KEY (`pid`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;  CREATE TABLE `shop_user` ( `uid` int NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(255) DEFAULT NULL COMMENT '用户名', `password` varchar(255) DEFAULT NULL COMMENT '密码', `telephone` varchar(255) DEFAULT NULL COMMENT '手机号', PRIMARY KEY (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建父工程&lt;/h2&gt; &lt;p&gt;创建一个maven工程，然后在pom.xml文件中添加下面内容&lt;/p&gt; &lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;      xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0           http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;parent&amp;gt;         &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;         &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;         &amp;lt;version&amp;gt;2.1.3.RELEASE&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;     &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;     &amp;lt;properties&amp;gt;         &amp;lt;java.version&amp;gt;1.8&amp;lt;/java.version&amp;gt;         &amp;lt;project.build.sourceEncoding&amp;gt;UTF-8&amp;lt;/project.build.sourceEncoding&amp;gt;         &amp;lt;project.reporting.outputEncoding&amp;gt;UTF- 8&amp;lt;/project.reporting.outputEncoding&amp;gt;         &amp;lt;spring-cloud.version&amp;gt;Greenwich.RELEASE&amp;lt;/spring-cloud.version&amp;gt;         &amp;lt;spring-cloud-alibaba.version&amp;gt;2.1.0.RELEASE&amp;lt;/spring-cloud-alibaba.version&amp;gt;     &amp;lt;/properties&amp;gt;     &amp;lt;dependencyManagement&amp;gt;         &amp;lt;dependencies&amp;gt;             &amp;lt;dependency&amp;gt;                 &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;spring-cloud-dependencies&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;${spring-cloud.version}&amp;lt;/version&amp;gt;                 &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;                 &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;             &amp;lt;/dependency&amp;gt;             &amp;lt;dependency&amp;gt;                 &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;                 &amp;lt;artifactId&amp;gt;spring-cloud-alibaba-dependencies&amp;lt;/artifactId&amp;gt;                 &amp;lt;version&amp;gt;${spring-cloud-alibaba.version}&amp;lt;/version&amp;gt;                 &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;                 &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;             &amp;lt;/dependency&amp;gt;         &amp;lt;/dependencies&amp;gt;     &amp;lt;/dependencyManagement&amp;gt; &amp;lt;/project&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;版本对应：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127205447364.png" alt="版本对应" title="版本对应" /&gt;&lt;/p&gt; &lt;h2&gt;创建基础模块&lt;/h2&gt; &lt;p&gt;创建 shop-common 模块:&lt;/p&gt; &lt;p&gt;在pom.xml中添加依赖&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;  &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0          http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;parent&amp;gt;         &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;         &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;         &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;shop-common&amp;lt;/artifactId&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;com.alibaba&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;fastjson&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.2.56&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;5.1.6&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;创建实体类&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Java"&gt;//用户 import lombok.Data; import javax.persistence.Id; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @Entity(name = &amp;quot;shop_user&amp;quot;) @Data public class User {     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Integer uid;//主键      private String username;//用户名      private String password;//密码      private String telephone;//手机号  }  //商品 import lombok.Data; import javax.persistence.Id; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @Entity(name = &amp;quot;shop_product&amp;quot;) @Data public class Product {     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Integer pid;//主键      private String pname;//商品名称      private Double pprice;//商品价格      private Integer stock;//库存  }  //订单 package cn.maruifu.vo;  import lombok.Data; import javax.persistence.Id;  import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType;  //订单 @Entity(name = &amp;quot;shop_order&amp;quot;) @Data public class Order {     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Long oid;//订单id      private Integer uid;//用户id      private String username;//用户名      private Integer pid;//商品ID       private String pname;//商品名称      private Double pprice;//商品价格      private Integer number;//数量  } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建用户微服务&lt;/h2&gt; &lt;p&gt;新建一个 shop-user 模块，然后进行下面操作&lt;/p&gt; &lt;h3&gt;创建pom.xml&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0          http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;      &amp;lt;parent&amp;gt;         &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;         &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;         &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;shop-user&amp;lt;/artifactId&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;shop-common&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;编写主类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication public class UserApplication {     public static void main(String[] args) {         SpringApplication.run(UserApplication.class, args);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;server:   port: 8071 spring:   application:     name: service-user   datasource:     url: jdbc:mysql://127.0.0.1:3306/spring-cloud?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: Mrf12345     driver-class-name: com.mysql.cj.jdbc.Driver   jpa:     database-platform: org.hibernate.dialect.MySQL5InnoDBDialect     show-sql: true     hibernate:       ddl-auto: update       use-new-id-generator-mappings: false  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建商品微服务&lt;/h2&gt; &lt;h3&gt;创建pom.xml&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;parent&amp;gt;         &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;         &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;         &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;shop-product&amp;lt;/artifactId&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;shop-common&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;编写主类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;@SpringBootApplication  public class ProductApplication {     public static void main(String[] args) {        SpringApplication.run(ProductApplication.class, args);     }  } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;server:   port: 8081 spring:   application:     name: service-product   datasource:     url: jdbc:mysql://shop?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: root     driver-class-name: com.mysql.cj.jdbc.Driver    jpa:     database-platform: org.hibernate.dialect.MySQL5InnoDBDialect     show-sql: true     hibernate:       ddl-auto: update       use-new-id-generator-mappings: false   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建ProductDao接口&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.dao;  import cn.maruifu.vo.Product; import org.springframework.data.jpa.repository.JpaRepository;  public interface ProductDao extends JpaRepository&amp;lt;Product,Integer&amp;gt;  { } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建ProductService类&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.service.impl;  import cn.maruifu.dao.ProductDao; import cn.maruifu.service.ProductService; import cn.maruifu.vo.Product; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;   @Service public class ProductServiceImpl implements ProductService {      @Autowired     private ProductDao productDao;       @Override     public Product findByPid(Integer pid) {         return productDao.findById(pid).get();     }  } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建Controller&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.controller;  import cn.maruifu.service.ProductService; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController;  @RestController @Slf4j public class ProductController {      @Autowired     private ProductService productService;     @GetMapping(&amp;quot;/product/{pid}&amp;quot;)     public Product product(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         Product product = productService.findByPid(pid);         log.info(&amp;quot;查询到商品:&amp;quot; + JSON.toJSONString(product));         return product;     }  } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;启动工程&lt;/h3&gt; &lt;p&gt;等到数据库表创建完毕之后，加入测试数据&lt;/p&gt; &lt;pre&gt;&lt;code class="language-sql"&gt;INSERT INTO shop_product VALUE(NULL,'小米','1000','5000');   INSERT INTO shop_product VALUE(NULL,'华为','2000','5000');   INSERT INTO shop_product VALUE(NULL,'苹果','3000','5000');   INSERT INTO shop_product VALUE(NULL,'OPPO','4000','5000'); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;通过浏览器访问服务&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127211736478.png" alt="image-20210127211736478" title="image-20210127211736478" /&gt;&lt;/p&gt; &lt;h2&gt;创建订单微服务&lt;/h2&gt; &lt;h3&gt;创建pom.xml&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt; &amp;lt;project xmlns=&amp;quot;http://maven.apache.org/POM/4.0.0&amp;quot;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;          xsi:schemaLocation=&amp;quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;quot;&amp;gt;     &amp;lt;parent&amp;gt;         &amp;lt;artifactId&amp;gt;springcloud-alibaba&amp;lt;/artifactId&amp;gt;         &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;         &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;/parent&amp;gt;     &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;     &amp;lt;artifactId&amp;gt;shop-order&amp;lt;/artifactId&amp;gt;     &amp;lt;dependencies&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;         &amp;lt;/dependency&amp;gt;         &amp;lt;dependency&amp;gt;             &amp;lt;groupId&amp;gt;cn.maruifu&amp;lt;/groupId&amp;gt;             &amp;lt;artifactId&amp;gt;shop-common&amp;lt;/artifactId&amp;gt;             &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;         &amp;lt;/dependency&amp;gt;     &amp;lt;/dependencies&amp;gt; &amp;lt;/project&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;编写主类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;package cn.maruifu;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication public class OrderApplication {     public static void main(String[] args) {          SpringApplication.run(OrderApplication.class, args);      } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;server:   port: 8091 spring:   application:     name: service-order   datasource:     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc:mysql://shop?serverTimezone=UTC&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;useSSL=true     username: root     password: root       jpa:     database-platform: org.hibernate.dialect.MySQL5InnoDBDialect     show-sql: true     hibernate:       ddl-auto: update       use-new-id-generator-mappings: false  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建OrderDao接口&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.dao;  import cn.maruifu.vo.Order; import org.springframework.data.jpa.repository.JpaRepository;  public interface OrderDao extends JpaRepository&amp;lt;Order,Long&amp;gt; {  } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建OrderService类&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.service.impl;  import cn.maruifu.dao.OrderDao; import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  @Service public class OrderServiceImpl implements OrderService {      @Autowired     private OrderDao orderDao;      @Override     public void save(Order order) {         orderDao.save(order);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建RestTemplate&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;  @SpringBootApplication public class OrderApplication {     public static void main(String[] args) {         SpringApplication.run(OrderApplication.class, args);     }       @Bean     public RestTemplate getRestTemplate() {         return new RestTemplate();     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;创建Controller&lt;/h3&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.controller;  import cn.maruifu.service.OrderService; import cn.maruifu.vo.Order; import cn.maruifu.vo.Product; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;  @RestController @Slf4j public class OrderController {      @Autowired     private RestTemplate restTemplate;     @Autowired     private OrderService orderService;     //准备买1件商品     @GetMapping(&amp;quot;/order/prod/{pid}&amp;quot;)     public Order order(@PathVariable(&amp;quot;pid&amp;quot;) Integer pid) {         log.info(&amp;quot;&amp;gt;&amp;gt;客户下单，这时候要调用商品微服务查询商品信息&amp;quot;);         //通过restTemplate调用商品微服务         Product product = restTemplate.getForObject(&amp;quot;http://localhost:8081/product/&amp;quot; + pid, Product.class);         log.info(&amp;quot;&amp;gt;&amp;gt;商品信息,查询结果:&amp;quot; + JSON.toJSONString(product));         Order order = new Order();         order.setUid(1);         order.setUsername(&amp;quot;测试用户&amp;quot;);         order.setPid(product.getPid());         order.setPname(product.getPname());         order.setPprice(product.getPprice());         order.setNumber(1);         orderService.save(order);         return order;     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;浏览器访问服务进行测试&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/27/image-20210127212306481.png" alt="image-20210127212306481" title="image-20210127212306481" /&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://github.com/MaRuifu/springcloud-alibaba/tree/main/springcloud-alibaba" target="_blank"&gt;源码地址&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 27 Jan 2021 13:27:00 GMT</pubDate>
    </item>
    <item>
      <title>微服务介绍</title>
      <link>https://maruifu.cn/article/178</link>
      <content:encoded>&lt;h2&gt;系统架构演变&lt;/h2&gt; &lt;p&gt;随着互联网的发展，网站应用的规模也在不断的扩大，进而导致系统架构也在不断的进行变化。从互联网早期到现在，系统架构大体经历了下面几个过程: 单体应用架构---&amp;gt;垂直应用架构---&amp;gt;分布式架构--- &amp;gt;SOA架构---&amp;gt;微服务架构，当然还有悄然兴起的Service Mesh(服务网格化)。接下来我们就来了解一下 每种系统架构是什么样子的， 以及各有什么优缺点。&lt;/p&gt; &lt;h3&gt;单体应用架构&lt;/h3&gt; &lt;p&gt;互联网早期，一般的网站应用流量较小，只需一个应用，将所有功能代码都部署在一起就可以，这样可以减少开发、部署和维护的成本。&lt;/p&gt; &lt;p&gt;比如说一个电商系统，里面会包含很多用户管理，商品管理，订单管理，物流管理等等很多模块，我们会把它们做成一个web项目，然后部署到一台tomcat服务器上。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/2021-01-25-8.45.08.png" alt="单体应用架构" title="单体应用架构" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;项目架构简单，小型项目的话， 开发成本低&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;项目部署在一个节点上， 维护方便&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;全部功能集成在一个工程中，对于大型项目来讲不易开发和维护&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;项目模块之间紧密耦合，单点容错率低&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;无法针对不同模块进行针对性优化和水平扩展&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;垂直应用架构&lt;/h3&gt; &lt;p&gt;随着访问量的逐渐增大，单一应用只能依靠增加节点来应对，但是这时候会发现并不是所有的模块都会有比较大的访问量。&lt;/p&gt; &lt;p&gt;还是以上面的电商为例子， 用户访问量的增加可能影响的只是用户和订单模块， 但是对消息模块 的影响就比较小. 那么此时我们希望只多增加几个订单模块， 而不增加消息模块. 此时单体应用就做不 到了， 垂直应用就应运而生了。&lt;/p&gt; &lt;p&gt;所谓的垂直应用架构，就是将原来的一个应用拆成互不相干的几个应用，以提升效率。比如我们可 以将上面电商的单体应用拆分成:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;电商系统(用户管理 商品管理 订单管理)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;后台系统(用户管理 订单管理 客户管理)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;CMS系统(广告管理 营销管理)&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;这样拆分完毕之后，一旦用户访问量变大，只需要增加电商系统的节点就可以了，而无需增加后台 和CMS的节点。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125204951263.png" alt="垂直应用架构" title="垂直应用架构" /&gt;&lt;/p&gt; &lt;h3&gt;分布式架构&lt;/h3&gt; &lt;p&gt;当垂直应用越来越多，重复的业务代码就会越来越多。这时候，我们就思考可不可以将重复的代码抽取 出来，做成统一的业务层作为独立的服务，然后由前端控制层调用不同的业务层服务呢?这就产生了新 的分布式系统架构。它将把工程拆分成表现层和服务层两个部分，服务层中包含业务逻辑。表现层只需 要处理和页面的交互，业务逻辑都是调用服务层的服务来实现。&lt;/p&gt; &lt;p&gt;​&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/11fab86aa65f081aee76e2f826f5d2ec.png" alt="分布式架构" title="分布式架构" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;抽取公共的功能为服务层，提高代码复用性&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;系统间耦合度变高，调用关系错综复杂，难以维护&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;SOA架构&lt;/h3&gt; &lt;p&gt;在分布式架构下，当服务越来越多，容量的评估，小服务资源的浪费等问题逐渐显现，此时需增加一个 调度中心对集群进行实时管理。此时，用于资源调度和治理中心(SOA Service OrientedArchitecture， 面向服务的架构)是关键。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125210744070.png" alt="SOA架构 " title="SOA架构 " /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;使用注册中心解决了服务间调用关系的自动调节&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;服务间会有依赖关系，一旦某个环节出错会影响较大( 服务雪崩 )&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;服务关心复杂，运维、测试部署困难&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;微服务架构&lt;/h3&gt; &lt;p&gt;微服务架构在某种程度上是面向服务的架构SOA继续发展的下一步，它更加强调服务的&amp;quot;彻底拆分&amp;quot;。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125210856385.png" alt="微服务架构" title="微服务架构" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;服务原子化拆分，独立打包、部署和升级，保证每个微服务清晰的任务划分，利于扩展&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;微服务之间采用Restful等轻量级http协议相互调用&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;分布式系统开发的技术成本高(容错、分布式事务等)&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;微服务架构介绍&lt;/h2&gt; &lt;p&gt;微服务架构， 简单的说就是将单体应用进一步拆分，拆分成更小的服务，每个服务都是一个可以独立运行的项目。&lt;/p&gt; &lt;h3&gt;微服务架构的常见问题&lt;/h3&gt; &lt;p&gt;一旦采用微服务系统架构，就势必会遇到这样几个问题:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;这么多小服务，如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;这么多小服务，他们之间如何通讯?(&lt;strong&gt;restful rpc&lt;/strong&gt; )&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;这么多小服务，客户端怎么访问他们?(网关)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;这么多小服务，一旦出现问题了，应该如何自处理?(容错)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;这么多小服务，一旦出现问题了，应该如何排错? (链路追踪)&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对于上面的问题，是任何一个微服务设计者都不能绕过去的，因此大部分的微服务产品都针对每一个问 题提供了相应的组件来解决它们。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/d5bc96a9d7a803a763b5da16c820abe6.png" alt="微服务架构" title="微服务架构" /&gt;&lt;/p&gt; &lt;h3&gt;微服务架构的常见概念&lt;/h3&gt; &lt;h4&gt;服务治理&lt;/h4&gt; &lt;p&gt;服务治理就是进行服务的自动化管理，其核心是服务的自动注册与发现。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务注册&lt;/strong&gt;:服务实例将自身服务信息注册到注册中心。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务发现&lt;/strong&gt;:服务实例通过注册中心，获取到注册到其中的服务实例的信息，通过这些信息去请求它们提 供的服务。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务剔除&lt;/strong&gt;:服务注册中心将出问题的服务自动剔除到可用列表之外，使其不会被调用到。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125212557404b0d5c0dce6ac5c00.png" alt="服务治理 " title="服务治理 " /&gt;&lt;/p&gt; &lt;h4&gt;服务调用&lt;/h4&gt; &lt;p&gt;在微服务架构中，通常存在多个服务之间的远程调用的需求。目前主流的远程调用技术有基于HTTP的RESTful接口以及基于TCP的RPC协议。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt;(Representational State Transfer):这是一种HTTP调用的格式，更标准，更通用，无论哪 种语言都支持http协议&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;RPC&lt;/strong&gt;(Remote Promote Call):一种进程间通信方式。允许像调用本地服务一样调用远程服务。 RPC框架的主要目标就是让远程服务调用更简单、透明。RPC框架负责屏蔽底层的传输方式、序列 化方式和通信细节。开发人员在使用的时候只需要了解谁在什么位置提供了什么样的远程服务接口 即可，并不需要关心底层通信细节和调用过程。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;strong&gt;区别与联系&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;比较项&lt;/th&gt;&lt;th&gt;RESTful&lt;/th&gt;&lt;th&gt;RPC&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;通讯协议&lt;/td&gt;&lt;td&gt;HTTP&lt;/td&gt;&lt;td&gt;一般使用TCP&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;性能&lt;/td&gt;&lt;td&gt;略低&lt;/td&gt;&lt;td&gt;较高&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;灵活度&lt;/td&gt;&lt;td&gt;高&lt;/td&gt;&lt;td&gt;较高&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;应用&lt;/td&gt;&lt;td&gt;微服务架构&lt;/td&gt;&lt;td&gt;SOA架构&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4&gt;服务网关&lt;/h4&gt; &lt;p&gt;随着微服务的不断增多，不同的微服务一般会有不同的网络地址，而外部客户端可能需要调用多个服务的接口才能完成一个业务需求，如果让客户端直接与各个微服务通信可能出现:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;客户端需要调用不同的url地址，增加难度&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在一定的场景下，存在跨域请求的问题&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;每个微服务都需要进行单独的身份认证&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;针对这些问题，API网关顺势而生。&lt;/p&gt; &lt;p&gt;API网关直面意思是将所有API调用统一接入到API网关层，由网关层统一接入和输出。一个网关的基本 功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后，各个 API服务提供团队可以专注于自己的的业务逻辑处理，而API网关更专注于安全、流量、路由等问题。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213156678.png" alt="API网关" title="API网关" /&gt;&lt;/p&gt; &lt;h4&gt;服务容错&lt;/h4&gt; &lt;p&gt;在微服务当中，一个请求经常会涉及到调用几个服务，如果其中某个服务不可用，没有做服务容错的 话，极有可能会造成一连串的服务不可用，这就是雪崩效应。&lt;strong&gt;我们没法预防雪崩效应的发生，只能尽可 能去做好容错。服务容错的三个核心思想是:&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;不被外界环境影响&lt;/li&gt; &lt;li&gt;不被上游请求压垮&lt;/li&gt; &lt;li&gt;不被下游响应拖垮&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213244700.png" alt="服务容错 " title="服务容错 " /&gt;&lt;/p&gt; &lt;h4&gt;链路追踪&lt;/h4&gt; &lt;p&gt;随着微服务架构的流行，服务按照不同的维度进行拆分，一次请求往往需要涉及到多个服务。互联网应 用构建在不同的软件模块集上，这些软件模块，有可能是由不同的团队开发、可能使用不同的编程语言 来实现、有可能布在了几千台服务器，横跨多个不同的数据中心。因此，就需要对一次请求涉及的多个 服务链路进行日志记录，性能监控即链路追踪&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213404309.png" alt="链路追踪" title="链路追踪" /&gt;&lt;/p&gt; &lt;h3&gt;微服务架构的常见解决方案&lt;/h3&gt; &lt;h4&gt;ServiceComb&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213505116.png" alt="ServiceComb" title="ServiceComb" /&gt;&lt;/p&gt; &lt;p&gt;Apache ServiceComb，前身是华为云的微服务引擎 CSE (Cloud Service Engine) 云服务，是全球首个 Apache微服务顶级项目。它提供了一站式的微服务开源解决方案，致力于帮助企业、用户和开发者将 企业应用轻松微服务化上云，并实现对微服务应用的高效运维管理。&lt;/p&gt; &lt;h4&gt;SpringCloud&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213629483.png" alt="SpringCloud" title="SpringCloud" /&gt;&lt;/p&gt; &lt;p&gt;Spring Cloud是一系列框架的集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设 施的开发，如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等，都可以用Spring Boot的开发风格做到一键启动和部署。&lt;/p&gt; &lt;p&gt;Spring Cloud并没有重复制造轮子，它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框 架组合起来，通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理，最终给开发者留出了 一套简单易懂、易部署和易维护的分布式系统开发工具包。&lt;/p&gt; &lt;h4&gt;SpringCloud Alibaba&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125213658821.png" alt="SpringCloud Alibaba" title="SpringCloud Alibaba" /&gt;&lt;/p&gt; &lt;p&gt;Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的 必需组件，方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。&lt;/p&gt; &lt;h2&gt;SpringCloud Alibaba介绍&lt;/h2&gt; &lt;p&gt;Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的 必需组件，方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba，您只需要添加一些注解和少量配置，就可以将 Spring Cloud 应用接入阿里微服 务解决方案，通过阿里中间件来迅速搭建分布式应用系统。&lt;/p&gt; &lt;h3&gt;主要功能&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务限流降级&lt;/strong&gt;:默认支持 WebServlet、WebFlux， OpenFeign、RestTemplate、Spring CloudGateway， Zuul， Dubbo 和 RocketMQ 限流降级功能的接入，可以在运行时通过控制台 实时修改限流降级规则，还支持查看限流降级 Metrics 监控。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;服务注册与发现&lt;/strong&gt;:适配 Spring Cloud 服务注册与发现标准，默认集成了 Ribbon 的支持。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;分布式配置管理&lt;/strong&gt;:支持分布式系统中的外部化配置，配置更改时自动刷新。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;消息驱动能力&lt;/strong&gt;:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;分布式事务&lt;/strong&gt;:使用 @GlobalTransactional 注解， 高效并且对业务零侵入地解决分布式事务问题。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;阿里云对象存储&lt;/strong&gt;:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任 何时间、任何地点存储和访问任意类型的数据。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;分布式任务调度&lt;/strong&gt;:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。 同时提供分布式的任务执行模型，如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;阿里云短信服务&lt;/strong&gt;:覆盖全球的短信服务，友好、高效、智能的互联化通讯能力，帮助企业迅速搭建 客户触达通道。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;组件&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Sentinel&lt;/strong&gt;:把流量作为切入点，从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳 定性。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Nacos&lt;/strong&gt;:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;:一款开源的分布式消息系统，基于高可用分布式集群技术，提供低延时的、高可靠 的消息发布与订阅服务。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Dubbo&lt;/strong&gt;:Apache DubboTM 是一款高性能 Java RPC 框架。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Seata&lt;/strong&gt;:阿里巴巴开源产品，一个易于使用的高性能微服务分布式事务解决方案。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Alibaba Cloud ACM&lt;/strong&gt;:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心 产品。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Alibaba Cloud OSS&lt;/strong&gt;: 阿里云对象存储服务(Object Storage Service，简称 OSS)，是阿里云提 供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和 访问任意类型的数据。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Alibaba Cloud SchedulerX&lt;/strong&gt;: 阿里中间件团队开发的一款分布式任务调度产品，提供秒级、精 准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;Alibaba Cloud SMS&lt;/strong&gt;: 覆盖全球的短信服务，友好、高效、智能的互联化通讯能力，帮助企业迅速 搭建客户触达通道。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Mon, 25 Jan 2021 13:42:00 GMT</pubDate>
    </item>
    <item>
      <title>Typora + uPic +Chevereto 完美组合</title>
      <link>https://maruifu.cn/article/177</link>
      <content:encoded>&lt;p&gt;对于用 &lt;code&gt;Markdown&lt;/code&gt; 来写博客的用户来说，图片的引用问题是个众所周知的难题。使用了这套组合后，妈妈再也不用担心我图片链接失效打不开了。&lt;/p&gt; &lt;h2&gt;工具下载安装：&lt;/h2&gt; &lt;p&gt;Typora ： &lt;a href="https://www.typora.io/" target="_blank"&gt;https://www.typora.io/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;uPic ： 从 &lt;a href="https://github.com/gee1k/uPic/releases" target="_blank"&gt;Github release&lt;/a&gt; 下载。如果访问 Github 下载困难的，可以从&lt;a href="https://gitee.com/gee1k/uPic/releases" target="_blank"&gt;Gitee release&lt;/a&gt;下载。&lt;/p&gt; &lt;p&gt;Chevereto ：&lt;a href="https://chevereto.com/" target="_blank"&gt;https://chevereto.com/&lt;/a&gt; ，群晖安装可以参考我之前的文章 &lt;a href="http://maruifu.cn/article/142" target="_blank"&gt;http://maruifu.cn/article/142&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;Typora上传设置&lt;/h2&gt; &lt;p&gt;插入图片时选择：&lt;code&gt;上传图片&lt;/code&gt;&lt;/p&gt; &lt;p&gt;勾选： &lt;code&gt;对本地位置的图片应用上述规则&lt;/code&gt;&lt;/p&gt; &lt;p&gt;勾选： &lt;code&gt;对网络位置的图片应用上述规则&lt;/code&gt;&lt;/p&gt; &lt;p&gt;上传服务选择 ：&lt;code&gt;uPic&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/24/image-20210124222726843.png" alt="image-20210124222726843" title="image-20210124222726843" /&gt;&lt;/p&gt; &lt;center style="font-size:14px;color:#C0C0C0;text-decoration:underline"&gt;Typora上传设置&lt;/center&gt;  &lt;h2&gt;uPic上传设置&lt;/h2&gt; &lt;p&gt;API Key: 在浏览器登录你的&lt;code&gt;Chevereto&lt;/code&gt;后，打开&lt;code&gt;仪表盘-&amp;gt;设置-&amp;gt;API&lt;/code&gt;。拷贝 API v1 Key&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/24/image-20210124224225336.png" alt="image-20210124224225336" title="image-20210124224225336" /&gt;&lt;/p&gt; &lt;center style="font-size:14px;color:#C0C0C0;text-decoration:underline"&gt;获取API Key&lt;/center&gt;  &lt;p&gt;API 地址：&lt;code&gt;[你的 Chevereto 地址]/api/1/upload。例如 https://demo.chevereto.com/api/1/upload&lt;/code&gt;&lt;/p&gt; &lt;p&gt;请求方式: &lt;code&gt;POST&lt;/code&gt; 使用 Base64: &lt;code&gt;勾选&lt;/code&gt; 文件字段名: &lt;code&gt;source&lt;/code&gt;&lt;/p&gt; &lt;p&gt;其他字段：&lt;/p&gt; &lt;p&gt;​ 增加Head­ers字段： &lt;code&gt;Content-Type&lt;/code&gt;:&lt;code&gt;multipart/form-data; charset=utf-8;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;​ 增加Body字段：&lt;/p&gt; &lt;p&gt;​   &lt;code&gt;key&lt;/code&gt;: &lt;code&gt;填写上面准备好的 [API Key]&lt;/code&gt;&lt;/p&gt; &lt;p&gt;​   &lt;code&gt;action&lt;/code&gt;: &lt;code&gt;upload&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/24/image-20210124223850177.png" alt="image-20210124223850177" title="image-20210124223850177" /&gt;&lt;/p&gt; &lt;center style="font-size:14px;color:#C0C0C0;text-decoration:underline"&gt;uPic其他字段设置&lt;/center&gt;  &lt;p&gt;URL 路径：&lt;code&gt;上传完成后获取图片链接的路径。['image', 'url']&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/24/image-20210124223314969.png" alt="image-20210124223314969" title="image-20210124223314969" /&gt;&lt;/p&gt; &lt;center style="font-size:14px;color:#C0C0C0;text-decoration:underline"&gt;uPic上传设置&lt;/center&gt;  &lt;h2&gt;自定义chevereto上传用户（可选）&lt;/h2&gt; &lt;h3&gt;获取root权限&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;admin@XiaoMageNAS:~$ sudo -i root@XiaoMageNAS:~#  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看运行的docker容器&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;root@XiaoMageNAS:~# docker ps -a CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                  PORTS                                                      NAMES f3e5f503f36d        bitwardenrs/server:1.18.0   &amp;quot;/start.sh&amp;quot;              4 days ago          Up 12 hours (healthy)   0.0.0.0:32769-&amp;gt;80/tcp, 0.0.0.0:32777-&amp;gt;3012/tcp             bitwardenrs 8f0cd2b10c84        gogs/gogs:latest            &amp;quot;/app/gogs/docker/st…&amp;quot;   5 weeks ago         Up 5 days               3000/tcp, 0.0.0.0:32768-&amp;gt;22/tcp, 0.0.0.0:32770-&amp;gt;3001/tcp   gogs-gogs1 c986eeb9d23d        portainer/portainer         &amp;quot;/portainer&amp;quot;             6 weeks ago         Up 5 days               0.0.0.0:9999-&amp;gt;9000/tcp                                     portainer 5bd176c39f27        nmtan/chevereto:latest      &amp;quot;docker-php-entrypoi…&amp;quot;   6 months ago        Up 12 hours             0.0.0.0:10000-&amp;gt;80/tcp                                      nmtan-chevereto1 &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;进入chevereto容器  &lt;code&gt;docker exec -it 5bd176c39f27 bash&lt;/code&gt;&lt;/p&gt; &lt;p&gt;退出容器chevereto容器 &lt;code&gt;exit&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;h3&gt;复制容器中的文件到本地目录&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; docker cp  5bd176c39f27:/var/www/html/app/routes/route.api.php /volume1/docker/ &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;vi /volume1/docker/route.api.php // 将此105行代码$uploaded_id = CHV\Image::uploadToWebsite($source);  修改为下面代码 $uploaded_id = CHV\Image::uploadToWebsite($source, 'xiaomage'); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/24/image-20210124234446010188058b36409ce01.png" alt="image-20210124234446010" title="image-20210124234446010" /&gt;&lt;/p&gt; &lt;p&gt;这里的&lt;code&gt;xiaomage&lt;/code&gt;是要传的用户名，可以设置-&amp;gt;账户-&amp;gt;用户名 查看&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/01/25/image-20210125001051434.png" alt="image-20210125001051434" title="image-20210125001051434" /&gt;&lt;/p&gt; &lt;h3&gt;复制到容器目录里面&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;docker cp /volume1/docker/route.api.php 5bd176c39f27:/var/www/html/app/routes/overrides/ &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 24 Jan 2021 15:58:00 GMT</pubDate>
    </item>
    <item>
      <title>Git的奇技淫巧</title>
      <link>https://maruifu.cn/article/176</link>
      <content:encoded>&lt;p&gt;Git是一个 “分布式版本管理工具”，简单的理解版本管理工具：大家在写东西的时候都用过 “回撤” 这个&lt;/p&gt; &lt;p&gt;功能，但是回撤只能回撤几步，假如想要找回我三天之前的修改，光用 “回撤” 是找不回来的。而 “版本&lt;/p&gt; &lt;p&gt;管理工具” 能记录每次的修改，只要提交到版本仓库，你就可以找到之前任何时刻的状态（文本状&lt;/p&gt; &lt;p&gt;态）。&lt;/p&gt; &lt;p&gt;下面的内容就是列举了常用的 Git 命令和一些小技巧，可以通过 &amp;quot;页面内查找&amp;quot; 的方式进行快速查询：&lt;/p&gt; &lt;p&gt;Ctrl/Command+f 。&lt;/p&gt; &lt;h2&gt;&lt;strong&gt;前言&lt;/strong&gt;&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;一定要先测试命令的效果后&lt;/strong&gt;，再用于工作环境中，以防造成不能弥补的后果！&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;所有的命令都在 git version 2.7.4 (Apple Git-66) 下测试通过&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;统一概念：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;工作区：改动（增删文件和内容）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;暂存区：输入命令： git add 改动的文件名 ，此次改动就放到了 ‘暂存区’&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;本地仓库(简称：本地)：输入命令： git commit 此次修改的描述 ，此次改动就放到了 ’本地&lt;/p&gt; &lt;p&gt;仓库’，每个 commit，我叫它为一个 ‘版本’。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;远程仓库(简称：远程)：输入命令： git push 远程仓库 ，此次改动就放到了 ‘远程仓&lt;/p&gt; &lt;p&gt;库’（GitHub 等)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;commit-id：输出命令： git log ，最上面那行 commit xxxxxx ，后面的字符串就是&lt;/p&gt; &lt;p&gt;commit-id&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;展示帮助信息&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git help -g   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The command output as below:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   The common Git guides are:       attributes          Defining attributes per path       cli                 Git command-line interface and conventions       core-tutorial       A Git core tutorial for developers       cvs-migration       Git for CVS users       diffcore            Tweaking diff output       everyday            A useful minimum set of commands for Everyday Git       glossary            A Git Glossary       hooks               Hooks used by Git       ignore              Specifies intentionally untracked files to ignore       modules             Defining submodule properties       namespaces          Git namespaces       repository-layout   Git Repository Layout       revisions           Specifying revisions and ranges for Git       tutorial            A tutorial introduction to Git       tutorial-2          A tutorial introduction to Git: part two       workflows           An overview of recommended workflows with Git     'git help -a' and 'git help -g' list available subcommands and some    concept guides. See 'git help &amp;lt;command&amp;gt;' or 'git help &amp;lt;concept&amp;gt;'    to read about a specific subcommand or concept.    See 'git help git' for an overview of the system. &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;回到远程仓库的状态&lt;/h2&gt; &lt;p&gt;抛弃本地所有的修改，回到远程仓库的状态。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git fetch --all &amp;amp;&amp;amp; git reset --hard origin/master &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;重设第一个commit&lt;/h2&gt; &lt;p&gt;也就是把所有的改动都重新放回工作区，并&lt;strong&gt;清空所有的&lt;/strong&gt; &lt;strong&gt;commit&lt;/strong&gt;，这样就可以重新提交第一个 commit&lt;/p&gt; &lt;p&gt;了&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git update-ref -d HEAD &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看冲突文件列表&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git diff --name-only --diff-filter=U &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示工作区和暂存区的不同&lt;/h2&gt; &lt;p&gt;输出&lt;strong&gt;工作区&lt;/strong&gt;和&lt;strong&gt;暂存区&lt;/strong&gt;的 difffferent (不同)。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git diff &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;还可以展示本地仓库中任意两个 commit 之间的文件变动：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git diff &amp;lt;commit-id&amp;gt; &amp;lt;commit-id&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示暂存区和最近版本的不同&lt;/h2&gt; &lt;p&gt;输出&lt;strong&gt;暂存区&lt;/strong&gt;和本地最近的版本 (commit) 的 difffferent (不同)。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git diff --cached &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示暂存区、工作区和最近版本的不同&lt;/h2&gt; &lt;p&gt;输出&lt;strong&gt;工作区&lt;/strong&gt;、&lt;strong&gt;暂存区&lt;/strong&gt; 和本地最近的版本 (commit) 的 difffferent (不同)。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git diff HEAD &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;快速切换到上一个分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout - &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除已经合并到 master 的分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git branch --merged master | grep -v '^\*\| master' | xargs -n 1 git branch -d &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示本地分支关联远程仓库的情况&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git branch -vv &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;关联远程分支&lt;/h2&gt; &lt;p&gt;关联之后， git branch -vv 就可以展示关联的远程分支名了，同时推送到远程仓库直接： git&lt;/p&gt; &lt;p&gt;push ，不需要指定远程仓库了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git branch -u origin/mybranch &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;或者在 push 时加上 -u 参数&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git push origin/mybranch -u &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;列出所有远程分支&lt;/h2&gt; &lt;p&gt;-r 参数相当于：remote&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git branch -r &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;列出本地和远程分支&lt;/h2&gt; &lt;p&gt;-a 参数相当于：all&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git branch -a &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看远程分支和本地分支的对应关系&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git remote show origin &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;远程删除了分支本地也想删除&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git remote prune origin &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;创建并切换到本地分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout -b &amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;从远程分支中创建并切换到本地分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout -b &amp;lt;branch-name&amp;gt; origin/&amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除本地分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git branch -d &amp;lt;local-branchname&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除远程分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git push origin --delete &amp;lt;remote-branchname&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;或者&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git push origin :&amp;lt;remote-branchname&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;重命名本地分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git branch -m &amp;lt;new-branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;重命名本地分支和远程分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   # 修改本地分支名    git branch -m &amp;lt;old-branch-name&amp;gt; &amp;lt;new-branch-name&amp;gt;    # 删除远程分支    git push origin --delete &amp;lt;old-branch-name&amp;gt;    # 改名后的本地分支推送到远程    git push --set-upstream origin &amp;lt;new-branch-name&amp;gt;  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看标签&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git tag &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;展示当前分支的最近的 tag&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git describe --tags --abbrev=0 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看标签详细信息&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git tag -ln &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;本地创建标签&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git tag &amp;lt;version-number&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;默认 tag 是打在最近的一次 commit 上，如果需要指定 commit 打 tag：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   $ git tag -a &amp;lt;version-number&amp;gt; -m &amp;quot;v1.0 发布(描述)&amp;quot; &amp;lt;commit-id&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;推送标签到远程仓库&lt;/h2&gt; &lt;p&gt;首先要保证本地创建好了标签才可以推送标签到远程仓库：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git push origin &amp;lt;local-version-number&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;一次性推送所有标签，同步到远程仓库&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git push origin --tags &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除本地标签&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git tag -d &amp;lt;tag-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除远程标签&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git push origin --delete tag &amp;lt;tagname&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;切回到某个标签&lt;/h2&gt; &lt;p&gt;一般上线之前都会打 tag，就是为了防止上线后出现问题，方便快速回退到上一版本。下面的命令是回&lt;/p&gt; &lt;p&gt;到某一标签下的状态：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git checkout -b branch_name tag_name &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;放弃工作区的修改&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout &amp;lt;file-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;放弃所有修改&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git checkout . &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;恢复删除的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git rev-list -n 1 HEAD -- &amp;lt;file_path&amp;gt; #得到 deleting_commit      git checkout &amp;lt;deleting_commit&amp;gt;^ -- &amp;lt;file_path&amp;gt; #回到删除文件 deleting_commit 之前的 状态 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;以新增一个 commit 的方式还原某一个 commit 的修改&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git revert &amp;lt;commit-id&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;回到某个 commit 的状态，并删除后面的 commit&lt;/h2&gt; &lt;p&gt;和 revert 的区别：reset 命令会抹去某个 commit id 之后的所有 commit&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git reset &amp;lt;commit-id&amp;gt; #默认就是-mixed参数。      git reset --mixed HEAD^ #回退至上个版本，它将重置HEAD到另外一个commit,并且重置暂存区以便 和HEAD相匹配，但是也到此为止。工作区不会被更改。      git reset --soft HEAD~3 #回退至三个版本之前，只回退了commit的信息，暂存区和工作区与回退之 前保持一致。如果还要提交，直接commit即可      git reset --hard &amp;lt;commit-id&amp;gt; #彻底回退到指定commit-id的状态，暂存区和工作区也会变为指定 commit-id版本的内容 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改上一个 commit 的描述&lt;/h2&gt; &lt;p&gt;如果暂存区有改动，同时也会将暂存区的改动提交到上一个 commit&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git commit --amend &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看 commit 历史&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git log &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看某段代码是谁写的&lt;/h2&gt; &lt;p&gt;blame 的意思为‘责怪’，你懂的。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git blame &amp;lt;file-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;显示本地更新过 HEAD 的 git 命令记录&lt;/h2&gt; &lt;p&gt;每次更新了 HEAD 的 git 命令比如 commit、amend、cherry-pick、reset、revert 等都会被记录下来&lt;/p&gt; &lt;p&gt;（不限分支），就像 shell 的 history 一样。&lt;/p&gt; &lt;p&gt;这样你可以 reset 到任何一次更新了 HEAD 的操作之后，而不仅仅是回到当前分支下的某个 commit 之&lt;/p&gt; &lt;p&gt;后的状态。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git reflog &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改作者名&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git commit --amend --author='Author Name &amp;lt;email@address.com&amp;gt;' &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改远程仓库的 url&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git remote set-url origin &amp;lt;URL&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;增加远程仓库&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git remote add origin &amp;lt;remote-url&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;列出所有远程仓库&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git remote &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查看两个星期内的改动&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git whatchanged --since='2 weeks ago' &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;把 A 分支的某一个 commit，放到 B 分支上&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout &amp;lt;branch-name&amp;gt; &amp;amp;&amp;amp; git cherry-pick &amp;lt;commit-id&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;给 git 命令起别名&lt;/h2&gt; &lt;p&gt;简化命令&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git config --global alias.&amp;lt;handle&amp;gt; &amp;lt;command&amp;gt;     比如：git status 改成 git st，这样可以简化命令     git config --global alias.st status &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;存储当前的修改，但不用提交 commit&lt;/h2&gt; &lt;p&gt;参考廖雪峰老师的&lt;a href="https://www.liaoxuefeng.com/wiki/896043488029600/900388704535136" target="_blank"&gt;教程&lt;/a&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git stash &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;保存当前状态，包括 untracked 的文件&lt;/h2&gt; &lt;p&gt;untracked 文件：新建的文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git stash -u &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示所有 stashes&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git stash list &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;回到某个 stash 的状态&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git stash apply &amp;lt;stash@{n}&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;回到最后一个 stash 的状态，并删除这个 stash&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git stash pop &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除所有的 stash&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git stash clear &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;从 stash 中拿出某个文件的修改&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git checkout &amp;lt;stash@{n}&amp;gt; -- &amp;lt;file-path&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示所有 tracked 的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git ls-files -t &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示所有 untracked 的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git ls-files --others &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示所有忽略的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git ls-files --others -i --exclude-standard &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;强制删除 untracked 的文件&lt;/h2&gt; &lt;p&gt;可以用来删除新建的文件。如果不指定文件文件名，则清空所有工作的 untracked 文件。 clean 命&lt;/p&gt; &lt;p&gt;令，&lt;strong&gt;注意两点&lt;/strong&gt;：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;clean 后，删除的文件无法找回&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;不会影响 tracked 的文件的改动，只会删除 untracked 的文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git clean &amp;lt;file-name&amp;gt; -f &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;强制删除 untracked 的目录&lt;/h2&gt; &lt;p&gt;可以用来删除新建的目录，&lt;strong&gt;注意&lt;/strong&gt;:这个命令也可以用来删除 untracked 的文件。详情见上一条&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git clean &amp;lt;directory-name&amp;gt; -df &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示简化的 commit 历史&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git log --pretty=oneline --graph --decorate --all &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;把某一个分支到导出成一个文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git bundle create &amp;lt;file&amp;gt; &amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;从包中导入分支&lt;/h2&gt; &lt;p&gt;新建一个分支，分支内容就是上面 git bundle create 命令导出的内容&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git clone repo.bundle &amp;lt;repo-dir&amp;gt; -b &amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;执行 rebase 之前自动 stash&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git rebase --autostash &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;从远程仓库根据 ID，拉下某一状态，到本地分支详细展示一行中的修改&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git fetch origin pull/&amp;lt;id&amp;gt;/head:&amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;详细展示一行中的修改&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git diff --word-diff &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;清除 .gitignore 文件中记录的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git clean -X -f &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示所有 alias 和 confifigs&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; confifig 分为：当前目录（local）和全局（golbal）的 confifig，默认为当前目录的 confifig&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git config --local --list (当前目录)     git config --global --list (全局) &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示忽略的文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git status --ignored &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;commit 历史中显示 Branch1 有的，但是 Branch2 没有 commit&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git log Branch1 ^Branch2 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;在 commit log 中显示 GPG 签名&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git log --show-signature &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;删除全局设置&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git config --global --unset &amp;lt;entry-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;新建并切换到新分支上，同时这个分支没有任何 commit&lt;/h2&gt; &lt;p&gt;相当于保存修改，但是重写 commit 历史&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git checkout --orphan &amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;展示任意分支某一文件的内容&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git show &amp;lt;branch-name&amp;gt;:&amp;lt;file-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;clone 下来指定的单一分支&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git clone -b &amp;lt;branch-name&amp;gt; --single-branch https://github.com/user/repo.git &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;clone 最新一次提交&lt;/h2&gt; &lt;p&gt;只会 clone 最近一次提交，将减少 clone 时间&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git clone --depth=1 https://github.com/user/repo.git &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;忽略某个文件的改动&lt;/h2&gt; &lt;p&gt;关闭 track 指定文件的改动，也就是 Git 将不会在记录这个文件的改动&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git update-index --assume-unchanged path/to/file &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;恢复 track 指定文件的改动&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git update-index --no-assume-unchanged path/to/file &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;忽略文件的权限变化&lt;/h2&gt; &lt;p&gt;不再将文件的权限变化视作改动&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git config core.fileMode false &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;以最后提交的顺序列出所有 Git 分支&lt;/h2&gt; &lt;p&gt;最新的放在最上面&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;在 commit log 中查找相关内容&lt;/h2&gt; &lt;p&gt;通过 grep 查找，given-text：所需要查找的字段&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git log --all --grep='&amp;lt;given-text&amp;gt;' &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;把暂存区的指定 fifile 放到工作区中&lt;/h2&gt; &lt;p&gt;不添加参数，默认是 -mixed&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   git reset &amp;lt;file-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;强制推送&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git push -f &amp;lt;remote-name&amp;gt; &amp;lt;branch-name&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;git 配置 http 和 socks 代理&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   git config --global https.proxy 'http://127.0.0.1:8001' # 适用于 privoxy 将 socks 协议转为 http 协议的 http 端口      git config --global http.proxy 'http://127.0.0.1:8001'      git config --global socks.proxy &amp;quot;127.0.0.1:1080&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;git 配置 ssh 代理&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;   $ cat ~/.ssh/config     Host gitlab.com     ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p # 直接使用 shadowsocks 提供的socks5 代理端口    Host github.com     ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;推送镜像&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git push --mirror  $URL &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;一图详解&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/02/03/V808ay.jpg" alt="git图解" title="git图解" /&gt;&lt;/p&gt; &lt;h2&gt;优雅的提交Commit信息&lt;/h2&gt; &lt;p&gt;使用&lt;a href="https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines" target="_blank"&gt;Angular团队提交规范&lt;/a&gt;&lt;/p&gt; &lt;p&gt;主要有以下组成&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;标题行: 必填, 描述主要修改类型和内容&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;主题内容: 描述为什么修改, 做了什么样的修改, 以及开发的思路等等&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;页脚注释: 放 Breaking Changes 或 Closed Issues&lt;/p&gt; &lt;p&gt;常用的修改项&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;type: commit 的类型&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;feat: 新特性&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;fifix: 修改问题&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;refactor: 代码重构&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;docs: 文档修改&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;style: 代码格式修改, 注意不是 css 修改&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;test: 测试用例修改&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;chore: 其他修改, 比如构建流程, 依赖管理.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;scope: commit 影响的范围, 比如: route, component, utils, build...&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;subject: commit 的概述&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;body: commit 具体修改内容, 可以分为多行&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;footer: 一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接.&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;使用&lt;strong&gt;Commitizen&lt;/strong&gt; 代替 git commit&lt;/h2&gt; &lt;p&gt;可以使用&lt;a href="https://github.com/commitizen/cz-cli" target="_blank"&gt;cz-cli&lt;/a&gt;工具代替 git commit&lt;/p&gt; &lt;p&gt;全局安装&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npm install -g commitizen &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;Commitizen适配器&lt;/h3&gt; &lt;p&gt;如果需要在项目中使用&lt;strong&gt;commitizen&lt;/strong&gt;生成符合AngularJS规范的&lt;strong&gt;提交说明&lt;/strong&gt;，初始化&lt;strong&gt;cz-conventional-changelog&lt;/strong&gt;适配器：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npm init -y commitizen init cz-conventional-changelog --save --save-exact &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果当前已经有其他适配器被使用，则会报以下错误，此时可以加上&lt;code&gt;--force&lt;/code&gt;选项进行再次初始化&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Error: A previous adapter is already configured. Use --force to override &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;全局安装后使用 &lt;code&gt;git cz&lt;/code&gt; 代替&lt;code&gt;git commit&lt;/code&gt; 就可以了,如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;? Select the type of change that you're committing: (Use arrow keys) ❯ feat:     A new feature    fix:      A bug fix    docs:     Documentation only changes    style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)    refactor: A code change that neither fixes a bug nor adds a feature    perf:     A code change that improves performance    test:     Adding missing tests or correcting existing tests &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;#查询全局node_modules路径 npm root -g #修改类型 node_modules/conventional-commit-types/index.json #修改描述 node_modules/cz-conventional-changelog/engine.js &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 21 Dec 2020 15:41:00 GMT</pubDate>
    </item>
    <item>
      <title>你还在new对象吗？Java8通用Builder了解一下？</title>
      <link>https://maruifu.cn/article/173</link>
      <content:encoded>&lt;p&gt;文章转载自: http://www.ciphermagic.cn/java8-builder.html&lt;/p&gt; &lt;p&gt;程序员经常会遇到灵魂拷问：你有对象吗？&lt;/p&gt; &lt;p&gt;没有，但我可以 new 一个！&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Java"&gt;public class GirlFriend {     private String name;     private int age;     // 省略 getter &amp;amp; setter ...     public static void main(String[] args) {         GirlFriend myGirlFriend = new GirlFriend();         myGirlFriend.setName(&amp;quot;小美&amp;quot;);         myGirlFriend.setAge(18);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;没问题，老铁！但如果对象的属性太多，咋办？&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;public class GirlFriend {      private String name;     private int age;     private int bust;     private int waist;     private int hips;     private List&amp;lt;String&amp;gt; hobby;     private String birthday;     private String address;     private String mobile;     private String email;     private String hairColor;     private Map&amp;lt;String, String&amp;gt; gift;     // 等等等等 ...      // 省略 getter &amp;amp; setter ...      public static void main(String[] args) {         GirlFriend myGirlFriend = new GirlFriend();         myGirlFriend.setName(&amp;quot;小美&amp;quot;);         myGirlFriend.setAge(18);         myGirlFriend.setBust(33);         myGirlFriend.setWaist(23);         myGirlFriend.setHips(33);         myGirlFriend.setBirthday(&amp;quot;2001-10-26&amp;quot;);         myGirlFriend.setAddress(&amp;quot;上海浦东&amp;quot;);         myGirlFriend.setMobile(&amp;quot;18688888888&amp;quot;);         myGirlFriend.setEmail(&amp;quot;pretty-xiaomei@qq.com&amp;quot;);         myGirlFriend.setHairColor(&amp;quot;浅棕色带点微卷&amp;quot;);         List&amp;lt;String&amp;gt; hobby = new ArrayList&amp;lt;&amp;gt;();         hobby.add(&amp;quot;逛街&amp;quot;);         hobby.add(&amp;quot;购物&amp;quot;);         hobby.add(&amp;quot;买东西&amp;quot;);         myGirlFriend.setHobby(hobby);         Map&amp;lt;String, String&amp;gt; gift = new HashMap&amp;lt;&amp;gt;();         gift.put(&amp;quot;情人节礼物&amp;quot;, &amp;quot;LBR 1912女王时代&amp;quot;);         gift.put(&amp;quot;生日礼物&amp;quot;, &amp;quot;迪奥烈焰蓝金&amp;quot;);         gift.put(&amp;quot;纪念日礼物&amp;quot;, &amp;quot;阿玛尼红管唇釉&amp;quot;);         myGirlFriend.setGift(gift);         // 等等等等 ...     } } &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code class="language-jAVA"&gt;GirlFriend{name='小美' , age=18 , bust=33 , waist=23 , hips=33 , hobby=[逛街, 购物, 买东西] , birthday='2001-10-26' , address='上海浦东' , mobile='18688888888' , email='pretty-xiaomei@qq.com' , hairColor='浅棕色带点微卷' , gift={情人节礼物=LBR 1912女王时代, 生日礼物=迪奥烈焰蓝金, 纪念日礼物=阿玛尼红管唇釉} }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;GirlFriend 是很美，但写起来也太麻烦了吧。&lt;/p&gt; &lt;p&gt;说说缺点：实例化和设置属性分开，不好维护；变量名重复写。&lt;/p&gt; &lt;p&gt;莫慌，看法宝~&lt;/p&gt; &lt;p&gt;这里不再介绍其他 Builder 实现方式，直接祭出最实用的&lt;strong&gt;通用Builder&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;适用于所有类，不需要改造原来类，不需要 lombok 插件支持。&lt;/p&gt; &lt;p&gt;先看看使用姿势：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-jAVA"&gt;public class GirlFriend {     // 省略属性 ...     // 省略 getter &amp;amp; setter ...          // 为了演示方便，加几个聚合方法     public void addHobby(String hobby) {         this.hobby = Optional.ofNullable(this.hobby).orElse(new ArrayList&amp;lt;&amp;gt;());         this.hobby.add(hobby);     }     public void addGift(String day, String gift) {         this.gift = Optional.ofNullable(this.gift).orElse(new HashMap&amp;lt;&amp;gt;());         this.gift.put(day, gift);     }     public void setVitalStatistics(int bust, int waist, int hips) {         this.bust = bust;         this.waist = waist;         this.hips = hips;     }     public static void main(String[] args) {         GirlFriend myGirlFriend = Builder.of(GirlFriend::new)                 .with(GirlFriend::setName, &amp;quot;小美&amp;quot;)                 .with(GirlFriend::setAge, 18)                 .with(GirlFriend::setVitalStatistics, 33, 23, 33)                 .with(GirlFriend::setBirthday, &amp;quot;2001-10-26&amp;quot;)                 .with(GirlFriend::setAddress, &amp;quot;上海浦东&amp;quot;)                 .with(GirlFriend::setMobile, &amp;quot;18688888888&amp;quot;)                 .with(GirlFriend::setEmail, &amp;quot;pretty-xiaomei@qq.com&amp;quot;)                 .with(GirlFriend::setHairColor, &amp;quot;浅棕色带点微卷&amp;quot;)                 .with(GirlFriend::addHobby, &amp;quot;逛街&amp;quot;)                 .with(GirlFriend::addHobby, &amp;quot;购物&amp;quot;)                 .with(GirlFriend::addHobby, &amp;quot;买东西&amp;quot;)                 .with(GirlFriend::addGift, &amp;quot;情人节礼物&amp;quot;, &amp;quot;LBR 1912女王时代&amp;quot;)                 .with(GirlFriend::addGift, &amp;quot;生日礼物&amp;quot;, &amp;quot;迪奥烈焰蓝金&amp;quot;)                 .with(GirlFriend::addGift, &amp;quot;纪念日礼物&amp;quot;, &amp;quot;阿玛尼红管唇釉&amp;quot;)                 // 等等等等 ...                 .build();     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;看到了吗！实例化和属性设置在同一条语句执行，链式操作，一路点点点，清爽！&lt;/p&gt; &lt;p&gt;Talk is cheap, show me the code：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;/**  * 通用的 Builder 模式构建器  *  * @author: CipherCui  * @since 2019/8/29  */ public class Builder&amp;lt;T&amp;gt; {     private final Supplier&amp;lt;T&amp;gt; instantiator;     private List&amp;lt;Consumer&amp;lt;T&amp;gt;&amp;gt; modifiers = new ArrayList&amp;lt;&amp;gt;();     public Builder(Supplier&amp;lt;T&amp;gt; instantiator) {         this.instantiator = instantiator;     }     public static &amp;lt;T&amp;gt; Builder&amp;lt;T&amp;gt; of(Supplier&amp;lt;T&amp;gt; instantiator) {         return new Builder&amp;lt;&amp;gt;(instantiator);     }     public &amp;lt;P1&amp;gt; Builder&amp;lt;T&amp;gt; with(Consumer1&amp;lt;T, P1&amp;gt; consumer, P1 p1) {         Consumer&amp;lt;T&amp;gt; c = instance -&amp;gt; consumer.accept(instance, p1);         modifiers.add(c);         return this;     }     public &amp;lt;P1, P2&amp;gt; Builder&amp;lt;T&amp;gt; with(Consumer2&amp;lt;T, P1, P2&amp;gt; consumer, P1 p1, P2 p2) {         Consumer&amp;lt;T&amp;gt; c = instance -&amp;gt; consumer.accept(instance, p1, p2);         modifiers.add(c);         return this;     }     public &amp;lt;P1, P2, P3&amp;gt; Builder&amp;lt;T&amp;gt; with(Consumer3&amp;lt;T, P1, P2, P3&amp;gt; consumer, P1 p1, P2 p2, P3 p3) {         Consumer&amp;lt;T&amp;gt; c = instance -&amp;gt; consumer.accept(instance, p1, p2, p3);         modifiers.add(c);         return this;     }     public T build() {         T value = instantiator.get();         modifiers.forEach(modifier -&amp;gt; modifier.accept(value));         modifiers.clear();         return value;     }     /**      * 1 参数 Consumer      */     @FunctionalInterface     public interface Consumer1&amp;lt;T, P1&amp;gt; {         void accept(T t, P1 p1);     }     /**      * 2 参数 Consumer      */     @FunctionalInterface     public interface Consumer2&amp;lt;T, P1, P2&amp;gt; {         void accept(T t, P1 p1, P2 p2);     }     /**      * 3 参数 Consumer      */     @FunctionalInterface     public interface Consumer3&amp;lt;T, P1, P2, P3&amp;gt; {         void accept(T t, P1 p1, P2 p2, P3 p3);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个示例最多支持三个参数的设置属性方法，也完全够用了。如果要扩展也很容易，依葫芦画瓢，添加多个参数的&lt;code&gt;Consumer&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;快用你的 Builder 建个对象吧~&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;lambok的@Accessors(chain = true)也可以使用链式结构&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sat, 14 Nov 2020 12:41:43 GMT</pubDate>
    </item>
    <item>
      <title>使用策略模式消除if else代码</title>
      <link>https://maruifu.cn/article/172</link>
      <content:encoded>&lt;h2&gt;需求&lt;/h2&gt; &lt;p&gt;业务需求是，有一个代报考系统，里面的一个功能是根据报考类目的不同维护不同的代报考规则。&lt;/p&gt; &lt;p&gt;代报考规则的实体：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;public class ExamRuleEditReqDTO extends BaseDTO {      // 无 则新增  有则 修改     private Integer  id;     //报考规则名称     @NotNull(message = &amp;quot;报考规则名称不能为空!&amp;quot;)     private String  name;      /**      *  报考类目  typeCode      *  消防工程师   HC_EXAM_TYPE_FIRE_ENGINEER      *  成人高考  HC_EXAM_TYPE_ADULT_EXAM      *  ACI 心理  HC_EXAM_TYPE_ACI_PSYCHOLOGY      *  ACI 营养  HC_EXAM_TYPE_ACI_NUTRITION      *  健康管理师 HC_EXAM_TYPE_HEALTH_MANAGER       *  自定义类目  HC_EXAM_TYPE_CUSTOMIZE      */     @NotNull(message = &amp;quot;报考类目Code不能为空!&amp;quot;)     private String  typeCode;     //是否免协报费     private String  exemptionAssistFlag;      // ... 省略 get / set ...       }     &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;service接口&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;public interface ExamService {      //维护代报考规则     ResponseBaseDTO&amp;lt;Integer&amp;gt; editExamRule(ExamRuleEditReqDTO reqDTO);      //查询代报考规则     ResponseBaseDTO&amp;lt;ExamRuleDetailQueryRespDTO&amp;gt; queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO);  }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;传统实现&lt;/h2&gt; &lt;p&gt;根据报考类目写一堆的&lt;code&gt;if else&lt;/code&gt;：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;@Service public class ExamServiceImpl implements ExamService {       @Autowired     private ExamRuleFireEngineerHandler examRuleFireEngineerHandler;      @Autowired     private ExamRuleAdultExamHandler examRuleAdultExamHandler;      @Autowired     private ExamRuleAciPsychologyHandler examRuleAciPsychologyHandler;      @Autowired     private ExamRuleAciNutritionHandler examRuleAciNutritionHandler;      @Autowired     private ExamRuleHealthManagerHandler examRuleHealthManagerHandler;      @Autowired     private ExamRuleCustomizeHandler examRuleCustomizeHandler;       public ResponseBaseDTO&amp;lt;Integer&amp;gt;  editExamRule(ExamRuleEditReqDTO reqDTO) {                if (ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER.equals(reqDTO.getTypeCode())) {             return examRuleFireEngineerHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ADULT_EXAM.equals(reqDTO.getTypeCode())) {             return examRuleAdultExamHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY.equals(reqDTO.getTypeCode())) {             return examRuleAciPsychologyHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION.equals(reqDTO.getTypeCode())) {             return examRuleAciNutritionHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER.equals(reqDTO.getTypeCode())) {             return examRuleHealthManagerHandler.editHandler(reqDTO);         }         return new ResponseBaseDTO&amp;lt;&amp;gt;(ResponseBaseDTO.FLAG_FAIL, &amp;quot;代报考类目参数不正确！&amp;quot;);     }           public ResponseBaseDTO&amp;lt;ExamRuleDetailQueryRespDTO&amp;gt;  queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO) {                if (ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER.equals(reqDTO.getTypeCode())) {             return examRuleFireEngineerHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ADULT_EXAM.equals(reqDTO.getTypeCode())) {             return examRuleAdultExamHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY.equals(reqDTO.getTypeCode())) {             return examRuleAciPsychologyHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION.equals(reqDTO.getTypeCode())) {             return examRuleAciNutritionHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER.equals(reqDTO.getTypeCode())) {             return examRuleHealthManagerHandler.editHandler(reqDTO);         } else if (ExamConstants.HC_EXAM_TYPE_CUSTOMIZE.equals(reqDTO.getTypeCode())) {             return examRuleHealthManagerHandler.editHandler(reqDTO);         }         return new ResponseBaseDTO&amp;lt;&amp;gt;(ResponseBaseDTO.FLAG_FAIL, &amp;quot;代报考类目参数不正确！&amp;quot;);     }  } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;策略模式实现&lt;/h2&gt; &lt;p&gt;利用策略模式，只需要两行即可实现业务逻辑：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Java"&gt;@Service public class ExamServiceImpl implements ExamService {         @Autowired     private ExamRuleHandlerContext examRuleHandlerContext;           @Override     @Transactional     public ResponseBaseDTO&amp;lt;Integer&amp;gt;  editExamRule(ExamRuleEditReqDTO reqDTO ) {         return this.examRuleHandlerContext.getHandlerInstance(reqDTO.getTypeCode()).editHandler(reqDTO );     }       @Override     public ResponseBaseDTO&amp;lt;ExamRuleDetailQueryRespDTO&amp;gt; queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO) {         return this.examRuleHandlerContext.getHandlerInstance(reqDTO.getTypeCode()).queryHandler(reqDTO);     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看到上面的方法中注入了ExamRuleHandlerContext，这是一个处理器上下文，用来保存不同的业务处理器，具体在下文会讲解。我们从中获取一个抽象的处理器AbstractExamRuleHandler，调用其方法实现业务逻辑。&lt;/p&gt; &lt;p&gt;现在可以了解到，我们主要的业务逻辑是在处理器中实现的，因此有多少个代报考类目，就对应有多少个处理器。以后需求变化，增加了代报考类目，只需要添加相应的处理器就可以，上述ExamServiceImpl完全不需改动。&lt;/p&gt; &lt;h4&gt;业务处理器的写法&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;@Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_CUSTOMIZE) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理自定义类目的规则          return null;     } }   @Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理健康管理师类目的规则          return null;     } }   @Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理ACI营养类目的规则          return null;     } }   @Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理ACI心理类目的规则          return null;     } }   @Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ADULT_EXAM) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理成人高考类目的规则          return null;     } }    @Component @ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER) public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {      @Override     public ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO){          //处理消防工程师类目的规则          return null;     } }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;首先每个处理器都必须添加到&lt;code&gt;spring&lt;/code&gt;容器中，因此需要加上&lt;code&gt;@Component&lt;/code&gt;注解，其次需要加上一个自定义注解&lt;code&gt;@ExamRuleHandler&lt;/code&gt;，用于标识该处理器对应哪个订单类型，最后就是继承AbstractExamRuleHandler，实现自己的业务逻辑。&lt;/p&gt; &lt;h4&gt;自定义注解 &lt;code&gt;@HandlerType&lt;/code&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-Java"&gt; import java.lang.annotation.*;  @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface ExamRuleHandler {     String value(); } &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;抽象处理器 &lt;code&gt;AbstractHandler&lt;/code&gt;&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;public abstract class AbstractExamRuleHandler {      public abstract ResponseBaseDTO&amp;lt;Integer&amp;gt; editHandler(ExamRuleEditReqDTO reqDTO );      public abstract ResponseBaseDTO&amp;lt;ExamRuleDetailQueryRespDTO&amp;gt; queryHandler(ExamRuleQueryReqDTO reqDTO ); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;自定义注解和抽象处理器都很简单，那么如何将处理器注册到&lt;code&gt;spring&lt;/code&gt;容器中呢？ 具体思路是：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;扫描指定包中标有&lt;code&gt;@ExamRuleHandler&lt;/code&gt;的类；&lt;/li&gt; &lt;li&gt;将注解中的类型值作为&lt;code&gt;key&lt;/code&gt;，对应的类作为&lt;code&gt;value&lt;/code&gt;，保存在&lt;code&gt;Map&lt;/code&gt;中；&lt;/li&gt; &lt;li&gt;重写 实现ApplicationContextAware接口的ExamRuleHandlerContext类中setApplicationContext方法，当spring容器初始化的时候，会自动的将ApplicationContext注入进来&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;我们将核心的功能封装在ExamRuleHandlerContext类中，完成上面的功能。&lt;/p&gt; &lt;h4&gt;上下文处理器ExamRuleHandlerContext&lt;/h4&gt; &lt;pre&gt;&lt;code class="language-Java"&gt;import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware;  import org.springframework.stereotype.Service;  import java.util.HashMap; import java.util.Map;  @Service public class ExamRuleHandlerContext implements ApplicationContextAware {      @Autowired     ApplicationContext applicationContext;       private static final Map&amp;lt;String,Class&amp;gt; handlerMap = new HashMap&amp;lt;&amp;gt;(10);      public AbstractExamRuleHandler getHandlerInstance(String typeCode){         Class clazz = handlerMap.get(typeCode);         return (AbstractExamRuleHandler) applicationContext.getBean(clazz);     }      @Override     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {         Map&amp;lt;String,Object&amp;gt; beans = applicationContext.getBeansWithAnnotation(ExamRuleHandler.class);         if (beans != null &amp;amp;&amp;amp; beans.size() &amp;gt; 0) {             for (Object serviceBean : beans.values()) {                 String payType = serviceBean.getClass().getAnnotation(ExamRuleHandler.class).value();                 handlerMap.put(payType, serviceBean.getClass());             }         }     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;code&gt;#getHandlerInstance方法根据类型获取对应的class，然后根据class类型获取注册到&lt;/code&gt;spring`中的bean。&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;最后请注意一点，ExamRuleHandlerContext 必须能被扫描到，或者通过&lt;code&gt;@Bean&lt;/code&gt;的方式显式的注册，才能在项目启动时发挥作用。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;利用策略模式可以简化繁杂的&lt;code&gt;if else&lt;/code&gt;代码，方便维护，而利用自定义注解和自注册的方式，可以方便应对需求的变更。本文只是提供一个大致的思路，还有很多细节可以灵活变化，例如使用枚举类型、或者静态常量，作为代报考的类型，相信你能想到更多更好的方法。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 14 Nov 2020 09:22:39 GMT</pubDate>
    </item>
    <item>
      <title>强大的 Stream API(三)</title>
      <link>https://maruifu.cn/article/171</link>
      <content:encoded>&lt;h2&gt;Stream 的终止操作&lt;/h2&gt; &lt;p&gt;终端操作会从流的流水线生成结果。其结果可以是任何不是流的 值，例如：List、Integer，甚至是 void 。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查找与匹配&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描 述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;allMatch(Predicate p)&lt;/td&gt;&lt;td align="left"&gt;检查是否匹配所有元素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;2&lt;/td&gt;&lt;td align="left"&gt;anyMatch(Predicate p)&lt;/td&gt;&lt;td align="left"&gt;检查是否至少匹配一个元素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;3&lt;/td&gt;&lt;td align="left"&gt;noneMatch(Predicate p)&lt;/td&gt;&lt;td align="left"&gt;检查是否没有匹配所有元素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;4&lt;/td&gt;&lt;td align="left"&gt;findFirst()&lt;/td&gt;&lt;td align="left"&gt;返回第一个元素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;5&lt;/td&gt;&lt;td align="left"&gt;findAny()&lt;/td&gt;&lt;td align="left"&gt;返回当前流中的任意元素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;6&lt;/td&gt;&lt;td align="left"&gt;count()&lt;/td&gt;&lt;td align="left"&gt;返回流中元素总数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;7&lt;/td&gt;&lt;td align="left"&gt;max(Comparator c)&lt;/td&gt;&lt;td align="left"&gt;返回流中最大值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;8&lt;/td&gt;&lt;td align="left"&gt;min(Comparator c)&lt;/td&gt;&lt;td align="left"&gt;返回流中最小值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;9&lt;/td&gt;&lt;td align="left"&gt;forEach(Consumer c)&lt;/td&gt;&lt;td align="left"&gt;内部迭代(使用 Collection 接口需要用户去做迭 代，称为外部迭代。相反，Stream API 使用内部 迭代——它帮你把迭代做了)&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt;       @Test     public void test1(){         boolean b1=employees.stream()//allMatch-检查是否匹配所有元素                 .allMatch((e)-&amp;gt;e.getStatus().equals(Status.BUSY));         System.out.println(b1);//false          boolean b2=employees.stream()//anyMatch-检查是否至少匹配一个元素                 .anyMatch((e)-&amp;gt;e.getStatus().equals(Status.BUSY));         System.out.println(b2);//true          boolean b3=employees.stream()//noneMatch-检查是否没有匹配所有元素                 .noneMatch((e)-&amp;gt;e.getStatus().equals(Status.BUSY));         System.out.println(b3);//false          Optional&amp;lt;Employee&amp;gt; op=employees.stream()//findFirst-返回第一个元素//Optional是Java8中避免空指针异常的容器类                 .sorted((e1,e2)-&amp;gt;Double.compare(e1.getSalary(), e2.getSalary()))                 .findFirst();         System.out.println(op.get());//Employee [name=王五, age=26, salary=3333.33, Status=VOCATION]          Optional&amp;lt;Employee&amp;gt; op2=employees.parallelStream()//findAny-返回当前流中的任意元素                 .filter((e)-&amp;gt;e.getStatus().equals(Status.FREE))                 .findAny();         System.out.println(op2.get());//Employee [name=赵六, age=36, salary=6666.66, Status=FREE]          Long count=employees.stream()//count-返回流中元素的总个数                 .count();         System.out.println(count);//5          Optional&amp;lt;Employee&amp;gt; op3=employees.stream()//max-返回流中最大值                 .max((e1,e2)-&amp;gt;Double.compare(e1.getSalary(), e2.getSalary()));         System.out.println(op3.get());//Employee [name=张三, age=18, salary=9999.99, Status=FREE]          Optional&amp;lt;Double&amp;gt; op4=employees.stream()//min-返回流中最小值                 .map(Employee::getSalary)                 .min(Double::compare);         System.out.println(op4.get());//3333.33     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;归约&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;reduce(T iden, BinaryOperator b)&lt;/td&gt;&lt;td align="left"&gt;可以将流中元素反复结合起来，得到一个值。返回 T&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;2&lt;/td&gt;&lt;td align="left"&gt;reduce(BinaryOperator b)&lt;/td&gt;&lt;td align="left"&gt;可以将流中元素反复结合起来，得到一个值。返回 Optional&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;blockquote&gt; &lt;p&gt;备注：map 和 reduce 的连接通常称为 map-reduce 模式，因 Google 用它 来进行网络搜索而出名&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code&gt;  /*      * 归约      * reduce(T identity,BinaryOperator b) / reduce(BinaryOperator b)-可以将流中元素反复结合起来，得到一个值。      */     @Test     public void test3(){         List&amp;lt;Integer&amp;gt; list=Arrays.asList(1,2,3,4,5,6,7,8,9,10);         Integer sum=list.stream()//reduce(T identity,BinaryOperator b)                         .reduce(0, (x,y)-&amp;gt;x+y);//0为起始值         System.out.println(sum);          System.out.println(&amp;quot;--------------------------&amp;quot;);          Optional&amp;lt;Double&amp;gt; op=employees.stream()//reduce(BinaryOperator b)//没有起始值，map返回可能为空，所以返回Optional类型                                      .map(Employee::getSalary)                                      .reduce(Double::sum);         System.out.println(op.get());     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;收集&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描 述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;collect(Collector c)&lt;/td&gt;&lt;td align="left"&gt;将流转换为其他形式。接收一个 Collector接口的 实现，用于给Stream中元素做汇总的方法&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法，可以方便地创建常见收集器实例，具体方法与实例如下表&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;方法&lt;/th&gt;&lt;th align="left"&gt;返回类型&lt;/th&gt;&lt;th align="left"&gt;作用&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;toList&lt;/td&gt;&lt;td align="left"&gt;List&lt;/td&gt;&lt;td align="left"&gt;把流中元素收集到List&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Listemps= list.stream().collect(Collectors.toList());&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;toSet&lt;/td&gt;&lt;td align="left"&gt;Set&lt;/td&gt;&lt;td align="left"&gt;把流中元素收集到Set&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Setemps= list.stream().collect(Collectors.toSet());&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;toCollection&lt;/td&gt;&lt;td align="left"&gt;Collection&lt;/td&gt;&lt;td align="left"&gt;把流中元素收集到创建的集合&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;counting&lt;/td&gt;&lt;td align="left"&gt;Long&lt;/td&gt;&lt;td align="left"&gt;计算流中元素的个数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;long count = list.stream().collect(Collectors.counting());&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;summingInt&lt;/td&gt;&lt;td align="left"&gt;Integer&lt;/td&gt;&lt;td align="left"&gt;对流中元素的整数属性求和&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;averagingInt&lt;/td&gt;&lt;td align="left"&gt;Double&lt;/td&gt;&lt;td align="left"&gt;计算流中元素Integer属性的平均 值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;summarizingInt&lt;/td&gt;&lt;td align="left"&gt;IntSummaryStatistics&lt;/td&gt;&lt;td align="left"&gt;收集流中Integer属性的统计值。如：平均值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;joining&lt;/td&gt;&lt;td align="left"&gt;String&lt;/td&gt;&lt;td align="left"&gt;连接流中每个字符串&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;String str= list.stream().map(Employee::getName).collect(Collectors.joining());&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;maxBy&lt;/td&gt;&lt;td align="left"&gt;Optional&lt;/td&gt;&lt;td align="left"&gt;根据比较器选择最大值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;minBy&lt;/td&gt;&lt;td align="left"&gt;Optional&lt;/td&gt;&lt;td align="left"&gt;根据比较器选择最小值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Optionalmin = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;reducing&lt;/td&gt;&lt;td align="left"&gt;归约产生的类型&lt;/td&gt;&lt;td align="left"&gt;从一个作为累加器的初始值 开始，利用BinaryOperator与 流中元素逐个结合，从而归 约成单个值 inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;collectingAndThen&lt;/td&gt;&lt;td align="left"&gt;转换函数返回的类型&lt;/td&gt;&lt;td align="left"&gt;包裹另一个收集器，对其结 果转换函数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;groupingBy&lt;/td&gt;&lt;td align="left"&gt;Map&amp;lt;k, list&lt;/td&gt;&lt;td align="left"&gt;根据某属性值对流分组，属 性为K，结果为V&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Map&amp;lt;emp.status, list&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;partitioningBy&lt;/td&gt;&lt;td align="left"&gt;Map&amp;lt;boolean, list&lt;/td&gt;&lt;td align="left"&gt;根据true或false进行分区&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Map&amp;lt;boolean,list&amp;gt;vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt;  /*      * 收集      * collect-将流转换为其他形式，接收一个Collector接口的实现，用于给Stream中元素做汇总的方法。      */     @Test     public void test4(){         List&amp;lt;String&amp;gt; list=employees.stream()                                    .map(Employee::getName)                                    .collect(Collectors.toList());         list.forEach(System.out::println);          System.out.println(&amp;quot;----------------------------&amp;quot;);          Set&amp;lt;String&amp;gt; set=employees.stream()                                  .map(Employee::getName)                                  .collect(Collectors.toSet());         set.forEach(System.out::println);          System.out.println(&amp;quot;----------------------------&amp;quot;);          HashSet&amp;lt;String&amp;gt; hs=employees.stream()                                     .map(Employee::getName)                                     .collect(Collectors.toCollection(HashSet::new));         hs.forEach(System.out::println);          System.out.println(&amp;quot;----------------------------&amp;quot;);          //总和         Long count=employees.stream()                             .collect(Collectors.counting());         System.out.println(count);          //平均值         Double avg=employees.stream()                             .collect(Collectors.averagingDouble(Employee::getSalary));         System.out.println(avg);          //总和         Double sum=employees.stream()                             .collect(Collectors.summingDouble(Employee::getSalary));         System.out.println(sum);          //最大值         Optional&amp;lt;Employee&amp;gt; max=employees.stream()                                         .collect(Collectors.maxBy((e1,e2)-&amp;gt;Double.compare(e1.getSalary(), e2.getSalary())));         System.out.println(max.get());          //最小值         Optional&amp;lt;Double&amp;gt; min=employees.stream()                                       .map(Employee::getSalary)                                       .collect(Collectors.minBy(Double::compare));         System.out.println(min.get());          System.out.println(&amp;quot;----------------------------&amp;quot;);          //分组         Map&amp;lt;Status,List&amp;lt;Employee&amp;gt;&amp;gt; map=employees.stream()                                                 .collect(Collectors.groupingBy(Employee::getStatus));         System.out.println(map);//{FREE=[Employee [name=张三, age=18, salary=9999.99, Status=FREE], Employee [name=赵六, age=36, salary=6666.66, Status=FREE]], VOCATION=[Employee [name=王五, age=26, salary=3333.33, Status=VOCATION]], BUSY=[Employee [name=李四, age=58, salary=5555.55, Status=BUSY], Employee [name=田七, age=12, salary=8888.88, Status=BUSY]]}          //多级分组         Map&amp;lt;Status,Map&amp;lt;String,List&amp;lt;Employee&amp;gt;&amp;gt;&amp;gt; map2=employees.stream()                                                             .collect( Collectors.groupingBy( Employee::getStatus,Collectors.groupingBy((e)-&amp;gt;{                                                                 if(e.getAge()&amp;lt;=35){                                                                     return &amp;quot;青年&amp;quot;;                                                                 }else if(e.getAge()&amp;lt;=50){                                                                     return &amp;quot;中年&amp;quot;;                                                                 }else{                                                                     return &amp;quot;老年&amp;quot;;                                                                 }                                                             }) ) );         System.out.println(map2);//{FREE={青年=[Employee [name=张三, age=18, salary=9999.99, Status=FREE]], 中年=[Employee [name=赵六, age=36, salary=6666.66, Status=FREE]]}, VOCATION={青年=[Employee [name=王五, age=26, salary=3333.33, Status=VOCATION]]}, BUSY={青年=[Employee [name=田七, age=12, salary=8888.88, Status=BUSY]], 老年=[Employee [name=李四, age=58, salary=5555.55, Status=BUSY]]}}          //分区         Map&amp;lt;Boolean,List&amp;lt;Employee&amp;gt;&amp;gt; map3=employees.stream()                                                  .collect(Collectors.partitioningBy((e)-&amp;gt;e.getSalary()&amp;gt;8000));         System.out.println(map3);//{false=[Employee [name=李四, age=58, salary=5555.55, Status=BUSY], Employee [name=王五, age=26, salary=3333.33, Status=VOCATION], Employee [name=赵六, age=36, salary=6666.66, Status=FREE]], true=[Employee [name=张三, age=18, salary=9999.99, Status=FREE], Employee [name=田七, age=12, salary=8888.88, Status=BUSY]]}          System.out.println(&amp;quot;--------------------------------&amp;quot;);          DoubleSummaryStatistics dss=employees.stream()                                              .collect(Collectors.summarizingDouble(Employee::getSalary));         System.out.println(dss.getSum());         System.out.println(dss.getAverage());         System.out.println(dss.getMax());          System.out.println(&amp;quot;--------------------------------&amp;quot;);         String strr=employees.stream()                              .map(Employee::getName)                              .collect(Collectors.joining(&amp;quot;,&amp;quot;));         System.out.println(strr);//张三李四王五赵六田七      } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 26 Oct 2020 11:55:08 GMT</pubDate>
    </item>
    <item>
      <title>强大的 Stream API(二)</title>
      <link>https://maruifu.cn/article/170</link>
      <content:encoded>&lt;h2&gt;Stream 的中间操作&lt;/h2&gt; &lt;p&gt;多个中间操作可以连接起来形成一个流水线，除非流水 线上触发终止操作，否则中间操作不会执行任何的处理！而在终止操作时一次性全部处理，称为“惰性求值”。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    //创建一个集合     List&amp;lt;Employee&amp;gt; employees=Arrays.asList(             new Employee(&amp;quot;张三&amp;quot;,18,9999.99),             new Employee(&amp;quot;李四&amp;quot;,58,5555.55),             new Employee(&amp;quot;王五&amp;quot;,26,3333.33),             new Employee(&amp;quot;赵六&amp;quot;,36,6666.66),             new Employee(&amp;quot;田七&amp;quot;,12,8888.88),             new Employee(&amp;quot;田七&amp;quot;,12,8888.88)      ); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;筛选与切片&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描 述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;filter(Predicate p)&lt;/td&gt;&lt;td align="left"&gt;接收 Lambda ， 从流中排除某些元素。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;2&lt;/td&gt;&lt;td align="left"&gt;distinct()&lt;/td&gt;&lt;td align="left"&gt;筛选，通过流所生成元素的 hashCode() 和 equals() 去 除重复元素。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;3&lt;/td&gt;&lt;td align="left"&gt;limit(long maxSize)&lt;/td&gt;&lt;td align="left"&gt;截断流，使其元素不超过给定数量。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;4&lt;/td&gt;&lt;td align="left"&gt;skip(long n)&lt;/td&gt;&lt;td align="left"&gt;跳过元素，返回一个扔掉了前 n 个元素的流。 若流中元素 不足 n 个，则返回一个空流。与 limit(n) 互补&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt; /*  筛选与切片      *  filter--接收Lambda，从流中排除某些元素。      *  limit--截断流，使其元素不超过给定数量。      *  skip(n)--跳过元素，返回一个扔掉了前n个元素的流。若流中元素不足n个，则返回一个空流。与limit(n) 互补      *  distinct--筛选，通过流所生成元素的 hashCode() 和 equals() 去掉重复元素      */ //内部迭代：迭代操作由 Stream API 完成 @Test public void test1(){     //中间操作：不会执行任何操作     Stream&amp;lt;Employee&amp;gt; stream=employees.stream()                             .filter((e) -&amp;gt; e.getAge()&amp;gt;35 );     //终止操作：一次性执行全部内容，即 惰性求值     stream.forEach(System.out::println); } //外部迭代 @Test public void test2(){     Iterator&amp;lt;Employee&amp;gt; it=employees.iterator();     while(it.hasNext()){         System.out.println(it.next());     } }  @Test public void test3(){//发现“短路”只输出了两次，说明只要找到 2 个 符合条件的就不再继续迭代     employees.stream()              .filter((e)-&amp;gt;{                  System.out.println(&amp;quot;短路！&amp;quot;);                  return e.getSalary()&amp;gt;5000;              })              .limit(2)              .forEach(System.out::println); }  @Test public void test4(){     employees.stream()              .filter((e)-&amp;gt;e.getSalary()&amp;gt;5000)              .skip(2)//跳过前两个              .distinct()//去重，注意：需要Employee重写hashCode 和 equals 方法              .forEach(System.out::println); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;映射&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描 述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;map(Function f)&lt;/td&gt;&lt;td align="left"&gt;接收一个函数作为参数，该函数会被应用到每个元 素上，并将其映射成一个新的元素。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;2&lt;/td&gt;&lt;td align="left"&gt;mapToDouble(ToDoubleFunction f)&lt;/td&gt;&lt;td align="left"&gt;接收一个函数作为参数，该函数会被应用到每个元 素上，产生一个新的 DoubleStream。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;3&lt;/td&gt;&lt;td align="left"&gt;mapToInt(ToIntFunction f)&lt;/td&gt;&lt;td align="left"&gt;接收一个函数作为参数，该函数会被应用到每个元 素上，产生一个新的 IntStream。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;4&lt;/td&gt;&lt;td align="left"&gt;mapToLong(ToLongFunction f)&lt;/td&gt;&lt;td align="left"&gt;接收一个函数作为参数，该函数会被应用到每个元 素上，产生一个新的 LongStream。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;5&lt;/td&gt;&lt;td align="left"&gt;flatMap(Function f)&lt;/td&gt;&lt;td align="left"&gt;接收一个函数作为参数，将流中的每个值都换成另 一个流，然后把所有流连接成一个流&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt; /*      * 映射      * map--接收Lambda，将元素转换成其他形式或提取信息。接收一个函数作为参数，该函数会被应用到每个元素上，并将其映射成一个新元素。      * flatMap--接收一个函数作为参数，将流中的每个值都换成另一个流，然后把所有流连接成一个流      */     @Test     public void test5(){         List&amp;lt;String&amp;gt; list=Arrays.asList(&amp;quot;aaa&amp;quot;,&amp;quot;bbb&amp;quot;,&amp;quot;ccc&amp;quot;,&amp;quot;ddd&amp;quot;);         list.stream()              .map((str)-&amp;gt;str.toUpperCase())              .forEach(System.out::println);          System.out.println(&amp;quot;------------------------&amp;quot;);          employees.stream()                  .map(Employee::getName)                  .forEach(System.out::println);          System.out.println(&amp;quot;------------------------&amp;quot;);          Stream&amp;lt;Stream&amp;lt;Character&amp;gt;&amp;gt; stream=list.stream()                                              .map(TestStreamAPI2::filterChatacter);         stream.forEach((sm)-&amp;gt;{             sm.forEach(System.out::println);         });          System.out.println(&amp;quot;------------------------&amp;quot;);          Stream&amp;lt;Character&amp;gt; sm=list.stream()                                  .flatMap(TestStreamAPI2::filterChatacter);         sm.forEach(System.out::println);     }      public static Stream&amp;lt;Character&amp;gt; filterChatacter(String str){         List&amp;lt;Character&amp;gt; list=new ArrayList&amp;lt;&amp;gt;();         for (Character ch : str.toCharArray()) {             list.add(ch);         }         return list.stream();     }      @Test     public void test6(){//map和flatMap的关系  类似于 add(Object)和addAll(Collection coll)         List&amp;lt;String&amp;gt; list=Arrays.asList(&amp;quot;aaa&amp;quot;,&amp;quot;bbb&amp;quot;,&amp;quot;ccc&amp;quot;,&amp;quot;ddd&amp;quot;);         List list2=new ArrayList&amp;lt;&amp;gt;();         list2.add(11);         list2.add(22);         list2.addAll(list);         System.out.println(list2);     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;排序&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;&lt;/th&gt;&lt;th align="left"&gt;方 法&lt;/th&gt;&lt;th align="left"&gt;描 述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;1&lt;/td&gt;&lt;td align="left"&gt;sorted()&lt;/td&gt;&lt;td align="left"&gt;产生一个新流，其中按自然顺序排序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;2&lt;/td&gt;&lt;td align="left"&gt;sorted(Comparator comp)&lt;/td&gt;&lt;td align="left"&gt;产生一个新流，其中按比较器顺序排序&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;pre&gt;&lt;code&gt;  //中间操作     /*      * 排序      * sorted()-自然排序（按照对象类实现Comparable接口的compareTo()方法 排序）      * sorted(Comparator com)-定制排序（Comparator）      */     @Test     public void test7(){         List&amp;lt;String&amp;gt; list=Arrays.asList(&amp;quot;ccc&amp;quot;,&amp;quot;bbb&amp;quot;,&amp;quot;aaa&amp;quot;);         list.stream()             .sorted()             .forEach(System.out::println);          System.out.println(&amp;quot;------------------------&amp;quot;);          employees.stream()                  .sorted((e1,e2)-&amp;gt;{                      if(e1.getAge().equals(e2.getAge())){                          return e1.getName().compareTo(e2.getName());                      }else{                          return e1.getAge().compareTo(e2.getAge());                      }                  }).forEach(System.out::println);       &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 26 Oct 2020 11:42:20 GMT</pubDate>
    </item>
    <item>
      <title>强大的 Stream API(一）</title>
      <link>https://maruifu.cn/article/169</link>
      <content:encoded>&lt;h2&gt;了解 Stream&lt;/h2&gt; &lt;p&gt;Java8中有两大最为重要的改变。第一个是 Lambda 表达式；另外一 个则是 Stream API(java.util.stream.*)。 Stream 是 Java8 中处理集合的关键抽象概念，它可以指定你希望对 集合进行的操作，可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作，就类似于使用 SQL 执行的数 据库查询。也可以使用 Stream API 来并行执行操作。简而言之， Stream API 提供了一种高效且易于使用的处理数据的方式。&lt;/p&gt; &lt;h2&gt;什么是 Stream&lt;/h2&gt; &lt;p&gt;流(Stream) 到底是什么呢？ 是数据渠道，用于操作数据源（集合、数组等）所生成的元素序列。 &lt;code&gt;“集合讲的是数据，流讲的是计算！”&lt;/code&gt;&lt;/p&gt; &lt;p&gt;注意：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;①Stream 自己不会存储元素。 ②Stream 不会改变源对象。相反，他们会返回一个持有结果的新Stream。 ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;Stream 的操作三个步骤&lt;/h2&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;创建 Stream&lt;/p&gt; &lt;p&gt;一个数据源（如：集合、数组），获取一个流&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;中间操作 一个中间操作链，对数据源的数据进行处理&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;终止操作(终端操作) 一个终止操作，执行中间操作链，并产生结果&lt;/p&gt; &lt;h3&gt;创建 Stream&lt;/h3&gt; &lt;p&gt;1.1可以通过Collection 系列集合提供的stream()或parallelStream()方法&lt;/p&gt; &lt;p&gt;default Stream&amp;lt; E&amp;gt; stream() : 返回一个顺序流 default Stream&amp;lt; E&amp;gt; parallelStream() : 返回一个并行流&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//可以通过Collection 系列集合提供的stream()或parallelStream() List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(); Stream&amp;lt;String&amp;gt; stream1 = list.stream(); Stream&amp;lt;String&amp;gt; stream2 = list.parallelStream(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;1.2通过 Arrays 中的静态方法stream()获取数组流&lt;/p&gt; &lt;p&gt;static &amp;lt; T&amp;gt; Stream&amp;lt; T&amp;gt; stream(T[] array): 返回一个流&lt;/p&gt; &lt;p&gt;重载形式，能够处理对应基本类型的数组：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; public static IntStream stream(int[] array) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;public static LongStream stream(long[] array)&lt;/p&gt; &lt;p&gt;public static DoubleStream stream(double[] array)&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//通过 Arrays 中的静态方法stream()获取数组流 Employee[] emps=new Employee[10]; Stream&amp;lt;Employee&amp;gt; stream3= Arrays.stream(emps); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;1.3通过Stream 类中的静态方法of()，通过显示值创建一个流。&lt;/p&gt; &lt;p&gt;它可以接收任意数量的参数。&lt;/p&gt; &lt;p&gt;public static&amp;lt; T&amp;gt; Stream&amp;lt; T&amp;gt; of(T… values) : 返回一个流&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//通过Stream 类中的静态方法of() Stream&amp;lt;String&amp;gt; stream4=Stream.of(&amp;quot;aa&amp;quot;,&amp;quot;bb&amp;quot;,&amp;quot;cc&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;1.4.创建无限流&lt;/p&gt; &lt;p&gt;可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。&lt;/p&gt; &lt;p&gt;迭代&lt;/p&gt; &lt;p&gt;public static&amp;lt; T&amp;gt; Stream&amp;lt; T&amp;gt; iterate(final T seed, final UnaryOperator&amp;lt; T&amp;gt; f)&lt;/p&gt; &lt;p&gt;生成&lt;/p&gt; &lt;p&gt;public static&amp;lt; T&amp;gt; Stream&amp;lt; T&amp;gt; generate(Supplier&amp;lt; T&amp;gt; s)&lt;/p&gt; &lt;pre&gt;&lt;code&gt;//创建无限流  //迭代 Stream&amp;lt;Integer&amp;gt; stream5=Stream.iterate(0, (x) -&amp;gt; x+2); stream4.limit(10).forEach(System.out::println);  //生成 Stream.generate(() -&amp;gt; Math.random())                .limit(5)                .forEach(System.out::println); &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Mon, 26 Oct 2020 11:39:44 GMT</pubDate>
    </item>
    <item>
      <title>域名工具网站</title>
      <link>https://maruifu.cn/article/168</link>
      <content:encoded>&lt;h3&gt;国内注册商,国内站长和企业喜欢的注册商&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://www.net.cn" target="_blank"&gt;www.net.cn&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;国内新后缀注册商&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://west.cn" target="_blank"&gt;west.cn&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://363.hk" target="_blank"&gt;363.hk&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://zzy.cn" target="_blank"&gt;zzy.cn&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;国内二位后缀域名注册商&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://quyu.net" target="_blank"&gt;quyu.net&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;国际注册商&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://Porkbun.com" target="_blank"&gt;Porkbun.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Dynadot.com" target="_blank"&gt;Dynadot.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Godaddy.com" target="_blank"&gt;Godaddy.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Namecheap.com" target="_blank"&gt;Namecheap.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Name.com" target="_blank"&gt;Name.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Namesilo.com" target="_blank"&gt;Namesilo.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://Uniregistry.com" target="_blank"&gt;Uniregistry.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://AllDomains.hosting" target="_blank"&gt;AllDomains.hosting&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;各后缀域名抢注平台&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;.com .net .org .cn .cc:  &lt;a href="http://juming.com" target="_blank"&gt;juming.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.info .mobi .asia .xxx .pro .red .black .bet .blue .ac .us ：&lt;a href="http://Dynadot.com" target="_blank"&gt;Dynadot.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;新后缀抢注平台&lt;/h3&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://Hexonet.net" target="_blank"&gt;Hexonet.net&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://zzy.cn" target="_blank"&gt;zzy.cn&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://west.cn" target="_blank"&gt;west.cn&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;国别后缀抢注平台&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;.ac .ag .bz .gg .io .je .ly .me .mn .sc .sh .to .vc ： &lt;a href="http://park.io" target="_blank"&gt;park.io&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.eu .be .ch .de .fr .it .li .nl .uk ： &lt;a href="http://catchtiger.com" target="_blank"&gt;catchtiger.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.es .it ： &lt;a href="http://nidoma.com" target="_blank"&gt;nidoma.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.nu .se ： &lt;a href="http://webb.se" target="_blank"&gt;webb.se&lt;/a&gt; 、&lt;a href="http://rymdweb.com" target="_blank"&gt;rymdweb.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.nz ： &lt;a href="http://expireddomains.co.nz" target="_blank"&gt;expireddomains.co.nz&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.id .ly .tw ： &lt;a href="http://docky.ly" target="_blank"&gt;docky.ly&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.ru .su ： &lt;a href="http://nic.ru" target="_blank"&gt;nic.ru&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;.cx .gs .hk: &lt;a href="http://363.hk" target="_blank"&gt;363.hk&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;查询工具&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;whois 查询： &lt;a href="http://who.is" target="_blank"&gt;who.is&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名溢价查询： &lt;a href="http://yijia.me" target="_blank"&gt;yijia.me&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;最终核实域名是不是可以注册： &lt;a href="http://domai.nr" target="_blank"&gt;domai.nr&lt;/a&gt;、&lt;a href="http://whois.domaintools.com" target="_blank"&gt;whois.domaintools.com&lt;/a&gt;、注册局&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;注册局官网以及联系方式查询：&lt;a href="https://www.iana.org/domains/root/db/xxx.html" target="_blank"&gt;https://www.iana.org/domains/root/db/xxx.html &lt;/a&gt;将 xxx 替换为某个后缀&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可注册域名扫描（米友作品）： &lt;a href="http://name.tg" target="_blank"&gt;name.tg&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名过期时间删除时间查询： &lt;a href="http://expireddomains.net" target="_blank"&gt;expireddomains.net&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名后缀注册量查询: &lt;a href="http://www.363.hk/web/registration" target="_blank"&gt;www.363.hk/web/registration&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;新后缀目前市场占有率查询： &lt;a href="http://namestat.org" target="_blank"&gt;namestat.org&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;全球每天有哪些域名被注册了： &lt;a href="http://dnpedia.com/tlds/daily.php" target="_blank"&gt;dnpedia.com/tlds/daily.php&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;国内有哪些后缀可以备案： &lt;a href="https://domain.miit.gov.cn/" target="_blank"&gt;域名.信息&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;国内平台域名成交价格查询： &lt;a href="http://wanmi.cc" target="_blank"&gt;wanmi.cc&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;国际域名成交价格查询： &lt;a href="http://namebio.com" target="_blank"&gt;namebio.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名在哪里注册最便宜： &lt;a href="http://tld-list.com" target="_blank"&gt;tld-list.com &lt;/a&gt;、&lt;a href="http://nazhumi.com" target="_blank"&gt;nazhumi.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名被墙污染查询： &lt;a href="http://zijian.aliyun.com/#/domainDetect" target="_blank"&gt;zijian.aliyun.com/#/domainDetect&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名建站历史查询： &lt;a href="http://archive.org" target="_blank"&gt;archive.org&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名 DNS 修改历史查询： &lt;a href="http://completedns.com" target="_blank"&gt;completedns.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名优惠活动： &lt;a href="http://Namebeta.com" target="_blank"&gt;Namebeta.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;域名备案查询: &lt;a href="http://micp.chinaz.com" target="_blank"&gt;micp.chinaz.com&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt;</content:encoded>
      <pubDate>Wed, 23 Sep 2020 14:13:00 GMT</pubDate>
    </item>
    <item>
      <title>mac下 IDEA 特殊的快捷键</title>
      <link>https://maruifu.cn/article/159</link>
      <content:encoded>&lt;h3&gt;“scroll from source”快捷方式 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;先按“Option + F1”，再按“Enter”或“1”键。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;打开终端 Terminal 的快捷键 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Option + F12 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;打开 project 的快捷键 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Command + 1 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;进入调用接口的快捷键 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Command + B &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;进入调用接口的实现方法快捷键 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Command + Option +  B &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;提取变量名 ？&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;Command + Option +  V &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 22 Sep 2020 04:24:00 GMT</pubDate>
    </item>
    <item>
      <title>MAC Navicat Premium Mac 12 破解(亲测可用!!!）</title>
      <link>https://maruifu.cn/article/156</link>
      <content:encoded>&lt;p&gt;首先先声明一下，我这个破解方法的版本是12.022(&lt;a href="https://www.navicat.com/en/products" target="_blank"&gt;官网&lt;/a&gt;最新的是15的,不清楚能不能,没测试),分享一波 &lt;a href="http://gofile.me/5dIGp/LzVY161YW" target="_blank"&gt;安装包下载&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Navicat Premium 是一套数据库开发工具，让你从单一应用程序中同时连接 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 数据库。它与 Amazon RDS、Amazon Aurora、Amazon Redshift、Microsoft Azure、Oracle Cloud、MongoDB Atlas、阿里云、腾讯云和华为云等云数据库兼容。你可以快速轻松地创建、管理和维护数据库。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;废话不多说,上破解方法.&lt;/p&gt; &lt;h1&gt;第一步：保存秘钥&lt;/h1&gt; &lt;p&gt;公钥：(存储为名为rpk的文件,备用)&lt;/p&gt; &lt;pre&gt;&lt;code&gt;-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQB8vXG0ImYhLHvHhpi5FS3g d2QhxSQiU6dQ04F1OHB0yRRQ3NXF5py2NNDw962i4WP1zpUOHh94/mg/KA8KHNJX HtQVLXMRms+chomsQCwkDi2jbgUa4jRFN/6N3QejJ42jHasY3MJfALcnHCY3KDEF h0N89FV4yGLyDLr+TLqpRecg9pkPnOp++UTSsxz/e0ONlPYrra/DiaBjsleAESZS I69sPD9xZRt+EciXVQfybI/2SYeAdXMm1B7tHCcFlOxeUgqYV03VEqiC0jVMwRCd +03NU3wvEmLBvGOmNGudocWIF/y3VOqyW1byXFLeZxl7s+Y/SthxOYXzu3mF+2/p AgMBAAE= -----END PUBLIC KEY----- &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;私钥：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQB8vXG0ImYhLHvHhpi5FS3gd2QhxSQiU6dQ04F1OHB0yRRQ3NXF 5py2NNDw962i4WP1zpUOHh94/mg/KA8KHNJXHtQVLXMRms+chomsQCwkDi2jbgUa 4jRFN/6N3QejJ42jHasY3MJfALcnHCY3KDEFh0N89FV4yGLyDLr+TLqpRecg9pkP nOp++UTSsxz/e0ONlPYrra/DiaBjsleAESZSI69sPD9xZRt+EciXVQfybI/2SYeA dXMm1B7tHCcFlOxeUgqYV03VEqiC0jVMwRCd+03NU3wvEmLBvGOmNGudocWIF/y3 VOqyW1byXFLeZxl7s+Y/SthxOYXzu3mF+2/pAgMBAAECggEAK5qZbYt8wenn1uZg 6onRwJ5bfUaJjApL+YAFx/ETtm83z9ByVbx4WWT7CNC7fK1nINy20/mJrOTZkgIx x6otiNC4+DIsACJqol+RLoo8I9pk77Ucybn65ZteOz7hVZIU+8j6LzW0KDt6yowX e75r7G/NEpfibNc3Zz81+oDd2x+bHyGbzc9QcePIVuEzkof6jgpbWrQZU14itx9l VxEgj/fbMccvBx8brR/l9ClmDZd9Y6TWsF1rfJpF3+DPeqFkKCiD7PGz3bs4O/Zd ZrfV21ZNVusBW49G6bU63gQVKsOf1qGo3efbAW1HVxgTQ/lExVdcMvdenZm+ADKp L4/wUQKBgQDOfBjn3OC2IerUFu18EgCS7pSjTSibXw+TeX3D5zwszLC091G2rGlT 5DihBUhMfesNdpoZynrs4YB6Sz9C3wSGAB8AM/tNvPhtSVtbMHmrdT2DEEKCvLkO RNBnt+8aTu2hGRanw9aL1189gzwrmXK5ZuuURfgLrB9ihrvjo4VznQKBgQCapx13 dEA1MwapBiIa3k8hVBCoGPsEPWqM33RBdUqUsP33f9/PCx00j/akwmjgQNnBlAJo Y7LOqPCyiwOkEf40T4IlHdzYntWQQvHhfBwqSgdkTE9tKj43Ddr7JVFRL6yMSbW3 9qAp5UX/+VzOLGAlfzJ8CBnkXwGrnKPCVbnZvQKBgQCd+iof80jlcCu3GteVrjxM LkcAbb8cqG1FWpVTNe4/JFgqDHKzPVPUgG6nG2CGTWxxv4UFKHpGE/11E28SHYjb cOpHAH5LqsGy84X2za649JkcVmtclUFMXm/Ietxvl2WNdKF1t4rFMQFIEckOXnd8 y/Z/Wcz+OTFF82l7L5ehrQKBgFXl9m7v6e3ijpN5LZ5A1jDL0Yicf2fmePUP9DGb ZTZbbGR46SXFpY4ZXEQ9GyVbv9dOT1wN7DXvDeoNXpNVzxzdAIt/H7hN2I8NL+4v EjHG9n4WCJO4v9+yWWvfWWA/m5Y8JqusV1+N0iiQJ6T4btrE4JSVp1P6FSJtmWOK W/T9AoGAcMhPMCL+N+AvWcYt4Y4mhelvDG8e/Jj4U+lwS3g7YmuQuYx7h5tjrS33 w4o20g/3XudPMJHhA3z+d8b3GaVM3ZtcRM3+Rvk+zSOcGSwn3yDy4NYlv9bdUj/4 H+aU1Qu1ZYojFM1Gmbe4HeYDOzRsJ5BhNrrV12h27JWkiRJ4F/Q= -----END RSA PRIVATE KEY----- &lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;第二步：安装navicat&lt;/h1&gt; &lt;p&gt;点击安装包一步一步安装即可&lt;/p&gt; &lt;h1&gt;第三步：替换公钥&lt;/h1&gt; &lt;p&gt;finder中，右键navicat ,显示包内容，打开目录 /Contents/Resources，编辑rpk文件，用第一步的公钥替换并保存。(如果该目录下面没有rpk文件,可以直接将第一步保存备用的rpk文件移动进去就行)&lt;/p&gt; &lt;h1&gt;第四步：断网&lt;/h1&gt; &lt;h1&gt;第五步：打开navicat&lt;/h1&gt; &lt;p&gt;根据navicat输入以下序列号：&lt;/p&gt; &lt;p&gt;中文版64位密钥序列号： NAVH-T4PX-WT8W-QBL5&lt;/p&gt; &lt;p&gt;英文版64位密钥序列号： NAVG-UJ8Z-EVAP-JAUW&lt;/p&gt; &lt;p&gt;如果注册码右边出现的 ✔️，那么恭喜你，可以继续往下进行了。如果右边是黑色的❌,那么请从第头再来一遍。&lt;/p&gt; &lt;h1&gt;第六步：手动激活&lt;/h1&gt; &lt;p&gt;由于断了网，则会出现激活失败，选择点击手动激活&lt;/p&gt; &lt;h1&gt;第七步：复制请求码&lt;/h1&gt; &lt;h1&gt;第八步：获取请求码明文&lt;/h1&gt; &lt;p&gt;登录这个网址  &lt;a href="http://tool.chacuo.net/cryptrsaprikey" target="_blank"&gt;http://tool.chacuo.net/cryptrsaprikey &lt;/a&gt;&lt;/p&gt; &lt;p&gt;在输入加密私钥位置 填上第一步的私钥&lt;/p&gt; &lt;p&gt;在待加密解密的文本 填上请求码(第七步中复制的)，点击RSA私钥解密。&lt;/p&gt; &lt;p&gt;请注意：如果没出现请求码明文可能是&lt;/p&gt; &lt;p&gt;1：网络问题，请多试几次&lt;/p&gt; &lt;p&gt;2：请检查第三部rpk文件是否替换成功！(如果没有出现很可能是这个原因,再确认一下)&lt;/p&gt; &lt;h1&gt;第九步：替换时间戳&lt;/h1&gt; &lt;p&gt;将请求码明文中的 K和DI的值替换到下面对应的地方，&lt;/p&gt; &lt;p&gt;{&amp;quot;K&amp;quot;:&amp;quot;NAVHT4PXWT8WQBL5&amp;quot;, &amp;quot;N&amp;quot;:&amp;quot;52pojie&amp;quot;, &amp;quot;O&amp;quot;:&amp;quot;52pojie.cn&amp;quot;, &amp;quot;DI&amp;quot;:&amp;quot;ODQ2Yjg2ZDBjMTEzMjhh&amp;quot;, &amp;quot;T&amp;quot;:1616939200}&lt;/p&gt; &lt;p&gt;再登录 &lt;a href="https://unixtime.51240.com/" target="_blank"&gt;https://unixtime.51240.com/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;调整到现在的时间(不用那么精确,没关系)，并把Unix时间戳替换到上面的T后面&lt;/p&gt; &lt;h1&gt;第十步：获取激活码&lt;/h1&gt; &lt;p&gt;将替换好的文本，放入在待加密解密的文本 中，点击RSA加密，得到加密后的文本&lt;/p&gt; &lt;h1&gt;第十一步：输入并点击激活&lt;/h1&gt; &lt;p&gt;输入激活码并点击激活。&lt;/p&gt; &lt;h6&gt;本文转载于&lt;a href="https://my.oschina.net/u/4271891/blog/4256494" target="_blank"&gt;https://my.oschina.net/u/4271891/blog/4256494&lt;/a&gt;&lt;/h6&gt;</content:encoded>
      <pubDate>Sun, 20 Sep 2020 12:54:00 GMT</pubDate>
    </item>
    <item>
      <title>macOS 下git+ Java + Maven + MySql + Nginx 开发环境</title>
      <link>https://maruifu.cn/article/155</link>
      <content:encoded>&lt;h2&gt;Git&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;官网下载：&lt;a href="http://git-scm.com/download/mac" target="_blank"&gt;http://git-scm.com/download/mac&lt;/a&gt;&lt;/li&gt; &lt;li&gt;安装过程和 Windows 没啥区别，都是下一步下一步。&lt;/li&gt; &lt;li&gt;IntelliJ IDEA 对 Git 的支持很好，也不需要额外配置什么，IntelliJ IDEA 的 Git 操作都很便捷强烈使用 IntelliJ IDEA 作为 Git 的 GUI 操作工具。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Homebrew 方式（推荐）&lt;/strong&gt;：&lt;code&gt;brew install git&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;JDK&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;官网下载 JDK7：&lt;a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html" target="_blank"&gt;http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html&lt;/a&gt;&lt;/li&gt; &lt;li&gt;官网下载 JDK8：&lt;a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html" target="_blank"&gt;http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html&lt;/a&gt; &lt;ul&gt; &lt;li&gt;jdk-8u231-macosx-x64.dmg&lt;/li&gt; &lt;li&gt;百度云（d8rj）：&lt;a href="https://pan.baidu.com/s/1VFAi0gpMWikTgjTQokZEhQ" target="_blank"&gt;https://pan.baidu.com/s/1VFAi0gpMWikTgjTQokZEhQ&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;Java 开发环境理论上一般都是这个优先安装的。&lt;/li&gt; &lt;li&gt;安装过程和 Windows 没啥区别，都是下一步下一步，只是比 Windows 简单，连安装路径都不需要改而已，所以这里不截图了。&lt;/li&gt; &lt;li&gt;我这边不管是 Windows、Mac、Linux，只要开发环境，JAVA_HOME 我都是 JDK8，同时还装有 JDK6、JDK7，在使用 IntelliJ IDEA 的时候，我可以同时使用三个版本的 JDK。&lt;/li&gt; &lt;li&gt;JDK 的环境变量是要添加的，我这边可以贴一下。&lt;/li&gt; &lt;li&gt;如果你是 bash，你需要编辑的是这个：&lt;code&gt;vim ~/.bash_profile&lt;/code&gt;&lt;/li&gt; &lt;li&gt;修改后之后刷新配置文件我是：&lt;code&gt;source ~/.bash_profile&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;# JDK 1.8 JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home JRE_HOME=$JAVA_HOME/jre PATH=$PATH:$JAVA_HOME/bin CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export JAVA_HOME export JRE_HOME export PATH export CLASSPATH &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;卸载 JDK&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;sudo rm -rf /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin  sudo rm -rf /Library/PreferencePanes/JavaControlPanel.prefPane  sudo rm -rf /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;IntelliJ IDEA&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;官网下载：&lt;a href="http://www.jetbrains.com/idea/" target="_blank"&gt;http://www.jetbrains.com/idea/&lt;/a&gt;&lt;/li&gt; &lt;li&gt;最优秀的 IDE，没有之一，我所有的生产力硬件设备都是为了支持它而购买的，所以内存一定要够大。&lt;/li&gt; &lt;li&gt;下面的 Maven、Tomcat 都是依赖于 IntelliJ IDEA 运行的，所以本质上我只要搞定 IntelliJ IDEA，其他的 Java 开发环境 IntelliJ IDEA 都会帮我们解决。&lt;/li&gt; &lt;li&gt;关于 IntelliJ IDEA Mac 下安装/配置等相关，请看我写的这个系列，里面有详细说明：&lt;a href="https://github.com/judasn/IntelliJ-IDEA-Tutorial" target="_blank"&gt;IntelliJ IDEA 简体中文专题教程&lt;/a&gt;&lt;/li&gt; &lt;li&gt;在 IntelliJ IDEA 有几个特别的地方我单独拿出来讲讲吧： &lt;ul&gt; &lt;li&gt;如果启动 Tomcat 的时候报：&lt;code&gt;Permission denied&lt;/code&gt;，你则可以：打开终端，进入 Tomcat\bin 目录，然后执行：&lt;code&gt;chmod 777 *.sh&lt;/code&gt;&lt;/li&gt; &lt;li&gt;如果启动 Tomcat 之后，控制台乱码了，并且你确认你在 IntelliJ IDEA 的 Preferences 中设置的控制台字体是支持中文的，那你可以尝试下在 Tomcat VM 参数上加上：&lt;code&gt;-Dfile.encoding=UTF-8&lt;/code&gt;&lt;/li&gt; &lt;li&gt;Git 的路径配置：&lt;code&gt;Preferences -- Version Control -- Git -- Path to Git executable&lt;/code&gt; 的值是：&lt;code&gt;/usr/local/git/bin/git&lt;/code&gt;&lt;/li&gt; &lt;li&gt;那你的 IntelliJ IDEA 终端路径可以改成 zsh 的，配置方法在 &lt;code&gt;Preferences -- Tools -- Terminal -- Shell path&lt;/code&gt; 的值改为是：&lt;code&gt;/bin/zsh&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;IntelliJ IDEA 在 Mac 下的配置文件保存路径 &lt;ul&gt; &lt;li&gt;下面内容中：&lt;code&gt;XXXXXX&lt;/code&gt;，表示 IntelliJ IDEA 的版本号，IntelliJ IDEA 的配置目录是跟版本号有关系的。&lt;/li&gt; &lt;li&gt;&lt;code&gt;/Users/你的用户名/Library/Application Support/IntelliJIdeaXXXXXX&lt;/code&gt;，用于保存安装的插件&lt;/li&gt; &lt;li&gt;&lt;code&gt;/Users/你的用户名/Library/Caches/IntelliJIdeaXXXXXX&lt;/code&gt;，用于保存缓存、日志、以及本地的版本控制信息（local history 这个功能）&lt;/li&gt; &lt;li&gt;&lt;code&gt;/Users/你的用户名/Library/Preferences/IntelliJIdeaXXXXXX&lt;/code&gt;，用于保存你的个人配置、授权文件，等价于 Windows 下的 &lt;code&gt;config&lt;/code&gt; 目录&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;IntelliJ IDEA 设置 JDK&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Maven&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;官网下载：&lt;a href="http://maven.apache.org/download.cgi" target="_blank"&gt;http://maven.apache.org/download.cgi&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Maven 是绿色版的，任何系统都适用。&lt;/li&gt; &lt;li&gt;安装方式和 Windows、Linux 没啥本质区别，都是把 zip 文件夹解压，然后新增几个系统变量，修改 Maven 配置文件参数。&lt;/li&gt; &lt;li&gt;我是把 Maven 解压后，直接把 Windows 的 settings.xml 复制过来，修改下该文件本地仓库的路径，其他没啥可以改的了。&lt;/li&gt; &lt;li&gt;然后本地仓库的那些依赖包是直接从 Windows 下拷贝过来的，这个是任何系统下都兼容的，不需要额外处理。&lt;/li&gt; &lt;li&gt;最后再用 IntelliJ IDEA 对 Maven 的配置路径重新做了修改。&lt;/li&gt; &lt;li&gt;以上这些点都需要你对 Maven 和 IntelliJ IDEA 有了解，对于这两个东西我也在本文章都贴了相关的文章链接，我这里不多说了，学习总是需要花时间的。&lt;/li&gt; &lt;li&gt;Maven 的环境变量是要添加的，我这边可以贴一下：&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;MAVEN_HOME=/Users/youmeek/my_software/work_software/maven3.3.9 PATH=$PATH:$MAVEN_HOME/bin export MAVEN_HOME export PATH &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;验证：&lt;code&gt;mvn -v&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;MySQL 5.7&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;官网下载 MySQL：&lt;a href="http://dev.mysql.com/downloads/mysql/" target="_blank"&gt;http://dev.mysql.com/downloads/mysql/&lt;/a&gt;&lt;/li&gt; &lt;li&gt;MySQL 5.7 版本：&lt;a href="https://dev.mysql.com/downloads/mysql/5.7.html#downloads" target="_blank"&gt;https://dev.mysql.com/downloads/mysql/5.7.html#downloads&lt;/a&gt;&lt;/li&gt; &lt;li&gt;MySQL 官网提供的 Mac 系统的安装包，是下一步下一步安装类型的，没啥难度，大家自己试一下。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;需要特别注意的是：安装结束后，会提示你它生成的一个随机密码，你要复制下，等下要用&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;有几个点需要注意的是： &lt;ul&gt; &lt;li&gt;如何修改 root 密码： &lt;ul&gt; &lt;li&gt;打开：&lt;code&gt;系统偏好设置 -- 底部的 MySQL -- 点击：Stop MySQL Server&lt;/code&gt;，根据提示输入你的 Mac 用户密码。&lt;/li&gt; &lt;li&gt;连接：&lt;code&gt;sudo /usr/local/mysql/bin/mysql -h 127.0.0.1 -u root -P 3306 -p&lt;/code&gt;，输入刚刚复制的密码 - 修改密码：&lt;code&gt;set password = password('123456');&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;MySQL 配置文件设置 &lt;ul&gt; &lt;li&gt;创建文件 &lt;code&gt;vim /etc/my.cnf&lt;/code&gt;，参考内容如下&lt;/li&gt; &lt;li&gt;重启 MySQL 服务即可&lt;/li&gt; &lt;li&gt;这里给一个 demo 示例：&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;[mysql] default-character-set = utf8mb4   [mysqld] symbolic-links=0 log-error=/var/log/mysql/error.log default-storage-engine = InnoDB collation-server = utf8mb4_unicode_520_ci init_connect = 'SET NAMES utf8mb4' character-set-server = utf8mb4 lower_case_table_names = 1 max_allowed_packet = 50M sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;通过命令行操作 MySQL&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;启动MySQL服务 sudo /usr/local/MySQL/support-files/mysql.server start  停止MySQL服务 sudo /usr/local/mysql/support-files/mysql.server stop  重启MySQL服务 sudo /usr/local/mysql/support-files/mysql.server restart &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;如果你要卸载&lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;sudo rm -rf /usr/local/mysql sudo rm -rf /Library/PreferencePanes/MySQL* sudo rm -rf /var/db/receipts/com.mysql.* &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;nginx&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;#当前目录 /Users/maruifu/work/ComponentServices  #下载nginx-1.18.0  wget http://nginx.org/download/nginx-1.18.0.tar.gz # 解压 tar -zxvf nginx-1.18.0.tar.gz #创建目录 mkdir nginx #创建模块目录 mkdir nginx-src #进入模板目录 cd nginx-src #下载pcre并解压 wget https://ftp.pcre.org/pub/pcre/pcre-8.41.tar.gz #下载openssl并解压 wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz  #下载zlib并解压 wget http://prdownloads.sourceforge.net/libpng/zlib-1.2.11.tar.gz #进入源码目录 cd ../nginx-1.18.0  #配置nginx ./configure  \ --with-http_gzip_static_module  \ --with-pcre=../nginx-src/pcre-8.41/  \ --with-http_ssl_module  \ --with-openssl=../nginx-src/openssl-1.1.0g  \ --with-zlib=../nginx-src/zlib-1.2.11  \ --prefix=/Users/maruifu/work/ComponentServices/nginx/nginx \  #编译并安装 make &amp;amp;&amp;amp; make install  &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;brew install wget&lt;/p&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Sun, 20 Sep 2020 10:26:00 GMT</pubDate>
    </item>
    <item>
      <title>Mac终端配置好的环境变量在关闭终端后失效怎么办</title>
      <link>https://maruifu.cn/article/154</link>
      <content:encoded>&lt;p&gt;原因 其默认启动执行脚本变为了&lt;code&gt;~/.zshrc&lt;/code&gt;，所以总会显示zsh：&lt;code&gt;xxx not found&lt;/code&gt;&lt;/p&gt; &lt;p&gt;解决方法 在终端中输入&lt;/p&gt; &lt;pre&gt;&lt;code&gt;echo ‘source ~/.bash_profile’ &amp;gt;&amp;gt; ~/.zshrc &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 20 Sep 2020 10:22:16 GMT</pubDate>
    </item>
    <item>
      <title>Awesome Mac</title>
      <link>https://maruifu.cn/article/153</link>
      <content:encoded>&lt;h1&gt;Awesome Mac&lt;/h1&gt; &lt;p&gt; 现在我们变得非常大，与最初的想法不同，如今我们需要收集各种类别非常好用的 Mac 应用程序、软件以及工具。&lt;/p&gt; &lt;h2&gt;开发者工具&lt;/h2&gt; &lt;h3&gt;编辑器&lt;/h3&gt; &lt;p&gt;&lt;em&gt;一种用于编辑纯文本文件的程序，建议使用免费开源的编辑器&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://atom.io" target="_blank"&gt;Atom&lt;/a&gt; - GitHub 推出的开源编辑器，&lt;a href="editor-plugin-zh.md#atom-plugin" target="_blank"&gt;Atom常用插件&lt;/a&gt;。&lt;/li&gt; &lt;li&gt;&lt;a href="https://developer.android.com/studio/index.html" target="_blank"&gt;Android Studio&lt;/a&gt; - Android 的官方 IDE，基于 Intellij IDEA。&lt;/li&gt; &lt;li&gt;&lt;a href="http://brackets.io" target="_blank"&gt;Brackets&lt;/a&gt; - Adobe 推出的 Brackets 免费/开源编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.barebones.com/products/bbedit/" target="_blank"&gt;BBEdit&lt;/a&gt; - 强大的文件编辑器，用于编辑文件，文本文件及程序源代码。&lt;/li&gt; &lt;li&gt;&lt;a href="http://panic.com/coda/" target="_blank"&gt;Coda2&lt;/a&gt; - 用于编写 Web 应用，长得漂亮的编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://coteditor.com" target="_blank"&gt;CotEditor&lt;/a&gt; - 轻量级的纯文本编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://chocolatapp.com/" target="_blank"&gt;Chocolat&lt;/a&gt; - 轻量级本地编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.decosoftware.com/" target="_blank"&gt;Deco IDE&lt;/a&gt; - React Native IDE 支持控件拖拽界面实时变更。&lt;/li&gt; &lt;li&gt;&lt;a href="http://espressoapp.com/" target="_blank"&gt;Espresso&lt;/a&gt; - Web 编程利器，具备了快速且强大的编辑功能、专业检查与分类、即时预览编辑成果、发布与同步功能等。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.emacswiki.org/emacs/EmacsForMacOS" target="_blank"&gt;Emacs&lt;/a&gt; - Emacs 是基于控制台的编辑器和高度可定制的。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.eclipse.org" target="_blank"&gt;Eclipse&lt;/a&gt; - 流行的开源 IDE，主要用于 Java，也为多种语言提供插件支持。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.sublimetext.com/3" target="_blank"&gt;Sublime Text&lt;/a&gt; - 一个比较简洁大方带插件管理系统的流行编辑器，&lt;a href="editor-plugin-zh.md#sublime-text-plugin" target="_blank"&gt;Sublime常用插件&lt;/a&gt;。&lt;/li&gt; &lt;li&gt;&lt;a href="http://haskellformac.com" target="_blank"&gt;Haskell for Mac&lt;/a&gt; - Haskell 的现代开发环境。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.dcloud.io/" target="_blank"&gt;HBuilder&lt;/a&gt; - HBuilder 是 DCloud（数字天堂）推出的一款支持 HTML5 的 Web 开发 IDE。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/toolbox/" target="_blank"&gt;JetBrains Toolbox App&lt;/a&gt; - 管理已安装的JetBrains工具，下载新工具并打开最近的项目。 &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/clion/" target="_blank"&gt;CLion&lt;/a&gt; - 强大的 C 和 C++ IDE。(&lt;strong&gt;学生免费&lt;/strong&gt;)&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.jetbrains.com/datagrip/" target="_blank"&gt;DataGrip&lt;/a&gt; - 用于数据库和SQL的跨平台IDE。 (&lt;strong&gt;学生免费&lt;/strong&gt;), 查看&lt;a href="https://www.jetbrains.com/student/" target="_blank"&gt;此处&lt;/a&gt;了解更多。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/rider/" target="_blank"&gt;Rider&lt;/a&gt; - 跨平台 C# IDE。 它是 Microsoft 的 Visual Studio 的替代方案.&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/objc/" target="_blank"&gt;AppCode&lt;/a&gt; - 适用于 iOS / macOS 开发的智能 IDE&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/pycharm/" target="_blank"&gt;PyCharm&lt;/a&gt; - 一款 Python 开发集成环境，有专业版和社区版。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/idea/" target="_blank"&gt;IntelliJ IDEA&lt;/a&gt; - 一款 Java 开发集成环境。(&lt;strong&gt;学生&lt;/strong&gt;免费)&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jetbrains.com/go/" target="_blank"&gt;GoLand&lt;/a&gt; - JetBrains出品的Go开发IDE，智能，灵活&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.jetbrains.com/webstorm/" target="_blank"&gt;Webstorm&lt;/a&gt; - 是 JetBrains 公司旗下一款 JavaScript 开发工具。&lt;strong&gt;学生&lt;/strong&gt;免费，&lt;a href="https://www.jetbrains.com/student/" target="_blank"&gt;点击这里&lt;/a&gt; 查看更多。 &lt;ul&gt; &lt;li&gt;&lt;a href="https://plugins.jetbrains.com/plugin/6098-nodejs" target="_blank"&gt;NodeJS&lt;/a&gt; - 集成 &lt;code&gt;Node.js&lt;/code&gt;，你肯定需要它，很多功能需要它。&lt;/li&gt; &lt;li&gt;&lt;a href="https://plugins.jetbrains.com/plugin/7294-editorconfig" target="_blank"&gt;EditorConfig&lt;/a&gt; - 帮助开发者在不同的编辑器和 IDE 之间定义和维护一致的代码风格。&lt;/li&gt; &lt;li&gt;&lt;a href="https://plugins.jetbrains.com/plugin/8006-material-theme-ui" target="_blank"&gt;Material Theme UI&lt;/a&gt; - Google 为 React 开发的主题。&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;&lt;a href="http://lighttable.com/" target="_blank"&gt;LightTable&lt;/a&gt; - 下一代代码编辑器。&lt;a href="https://github.com/LightTable/LightTable" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://micro-editor.github.io" target="_blank"&gt;micro&lt;/a&gt; - 一个现代直观的基于终端的文本编辑器。 &lt;a href="https://github.com/ory/editor" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://netbeans.org/" target="_blank"&gt;NetBeans IDE&lt;/a&gt; - 免费、开源的 IDE，主要用于 Java 开发，可支持多种语言和框架。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/onivim/oni" target="_blank"&gt;ONI&lt;/a&gt; - 由 Neovim 提供的 IDE。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www1.qt.io/cn/" target="_blank"&gt;Qt&lt;/a&gt; - 跨平台 C++ 图形用户界面应用程序开发框架。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macromates.com" target="_blank"&gt;TextMate&lt;/a&gt; - 文本编辑器软件，与 BBedit 一起并称苹果机上的 emacs 和 vim。&lt;a href="https://github.com/textmate/textmate" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://codingfriends.github.io/Tincta/" target="_blank"&gt;Tincta&lt;/a&gt; - 一个免费的文本编辑器。  开源地址](https://github.com/CodingFriends/Tincta)&lt;/li&gt; &lt;li&gt;&lt;a href="https://code.visualstudio.com/" target="_blank"&gt;Visual Studio Code&lt;/a&gt; - 微软推出的免费/开源编辑器，TypeScript 支持杠杠的，&lt;a href="editor-plugin-zh.md#vscode-plugin" target="_blank"&gt;VSCode常用插件&lt;/a&gt;。&lt;a href="https://github.com/Microsoft/vscode" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.vim.org/" target="_blank"&gt;Vim&lt;/a&gt; - Vim 古老的终端中使用的编辑器，&lt;a href="editor-plugin-zh.md#vim-plugin" target="_blank"&gt;Vim常用插件&lt;/a&gt;。&lt;a href="https://github.com/vim/vim" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://vimr.org/" target="_blank"&gt;Vimr&lt;/a&gt; - Vim 客户端，升级 Vim 体验。&lt;a href="https://github.com/qvacua/vimr/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.visualstudio.com/vs/visual-studio-mac/" target="_blank"&gt;Visual Studio Community for Mac&lt;/a&gt; - 免费，开源，功能齐全的 IDE。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.visualstudio.com/vs/visual-studio-mac/" target="_blank"&gt;Xamarin Studio&lt;/a&gt; - 免费的跨平台的 C# IDE。支持 IOS、Android 和 .net 开发。&lt;a href="https://github.com/mono/monodevelop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://developer.apple.com/xcode/" target="_blank"&gt;Xcode&lt;/a&gt; - 开发 iOS 和 MacOS 基本 IDE。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;开发者实用工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.publicspace.net/BetterRename/" target="_blank"&gt;BetterRename&lt;/a&gt; - 一款强大的批量重命名工具，可以通过搜索功能改名。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.scootersoftware.com/download.php" target="_blank"&gt;Beyond Compare&lt;/a&gt; - 对比两个文件夹或者文件，并将差异以颜色标示。&lt;/li&gt; &lt;li&gt;&lt;a href="https://codekitapp.com/" target="_blank"&gt;CodeKit&lt;/a&gt; - 自动编译 Less、Sass、Stylus、CoffeeScript、Jade &amp;amp; Haml等文件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.cacher.io/" target="_blank"&gt;Cacher&lt;/a&gt; - 基于云的团队代码片段管理器，具有Gist同步，VSCode/Atom/Sublime软件包和Mac/Windows/Linux/Web客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://kapeli.com/dash" target="_blank"&gt;Dash&lt;/a&gt; - 强大到你无法想象的 API 离线文档软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://sourcegear.com/diffmerge/" target="_blank"&gt;DiffMerge&lt;/a&gt; - 可视化的文件比较（也可进行目录比较）与合并工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/hschmidt/EnvPane" target="_blank"&gt;EnvPane&lt;/a&gt; - 图形终端查看环境变量的应用工具。&lt;a href="https://github.com/hschmidt/EnvPane" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/TencentOpen/Fanvas" target="_blank"&gt;Fanvas&lt;/a&gt; - 把 swf 转为 HTML5 canvas 动画的系统。&lt;a href="https://github.com/oklai/koala/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/onmyway133/FinderGo" target="_blank"&gt;FinderGo&lt;/a&gt; Finder 中快速打开终端，定位到目录 &lt;a href="https://github.com/onmyway133/FinderGo" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/2ndalpha/gasmask" target="_blank"&gt;Gas Mask&lt;/a&gt; - 编辑 hosts 文件的工具，更简单方便。&lt;a href="https://github.com/2ndalpha/gasmask" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://zipzapmac.com/Go2Shell" target="_blank"&gt;Go2Shell&lt;/a&gt; - 从 Finder 打开命令行。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macpaw.com/gemini" target="_blank"&gt;Gemini&lt;/a&gt; - 智能的重复文件查找器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/specialunderwear/Hosts.prefpane" target="_blank"&gt;Hosts.prefpane&lt;/a&gt; - 编辑 hosts 文件的工具。&lt;a href="https://github.com/specialunderwear/Hosts.prefpane" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://ridiculousfish.com/hexfiend/" target="_blank"&gt;Hex Fiend&lt;/a&gt; - 快速而聪明的开源十六进制编辑器。 &lt;a href="https://github.com/ridiculousfish/HexFiend/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://en.toolinbox.net/iHosts/" target="_blank"&gt;iHosts&lt;/a&gt; - 唯一上架 Mac App Store 的 /etc/hosts 编辑神器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://peacockmedia.software/mac/integrity/free.html" target="_blank"&gt;Integrity&lt;/a&gt; - 轻松找到无效链接。&lt;/li&gt; &lt;li&gt;&lt;a href="http://koala-app.com" target="_blank"&gt;Koala&lt;/a&gt; - 预处理器语言图形编译工具，支持 Less、Sass、CoffeeScript、Compass framework 的即时编译。&lt;a href="https://github.com/oklai/koala/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.kaleidoscopeapp.com/" target="_blank"&gt;Kaleidoscope&lt;/a&gt; - 一款很强大的文本文件和图像比较工具，同时和 git、svn 等版本控制工具能够完美的结合。&lt;/li&gt; &lt;li&gt;&lt;a href="http://localname.io/" target="_blank"&gt;Localname&lt;/a&gt; - 提供对本地开发服务器的访问权限。&lt;/li&gt; &lt;li&gt;&lt;a href="https://mjmlio.github.io/mjml-app/" target="_blank"&gt;MJML&lt;/a&gt; - 简化设计回应电子邮件的方式。&lt;a href="https://github.com/mjmlio/mjml" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.paintcodeapp.com/" target="_blank"&gt;PaintCode&lt;/a&gt; - 将设计转换成 Objective-C, Swift 或 C# 代码。&lt;/li&gt; &lt;li&gt;&lt;a href="https://pushmate.app" target="_blank"&gt;PushMate&lt;/a&gt; 可通过确保推送有效载荷正确来解决常见的推送通知问题。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/jkpang/PPRows" target="_blank"&gt;PPRows&lt;/a&gt; - 计算你写了多少行代码。&lt;a href="https://github.com/jkpang/PPRows" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://oldj.github.io/SwitchHosts/" target="_blank"&gt;SwitchHosts&lt;/a&gt; - 一个管理、切换多个 hosts 方案的工具。&lt;a href="https://github.com/oldj/SwitchHosts" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/scmbreeze/scm_breeze" target="_blank"&gt;SCM Breeze&lt;/a&gt; - 用于增强与git交互的shell脚本集(用于bash和zsh)。&lt;a href="https://github.com/scmbreeze/scm_breeze" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.renfei.org/snippets-lab/" target="_blank"&gt;SnippetsLab&lt;/a&gt; - 管理和组织你的代码片段。&lt;/li&gt; &lt;li&gt;&lt;a href="http://staruml.io" target="_blank"&gt;StarUML&lt;/a&gt; - 强大的软件建模软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.vandyke.com/products/securecrt/" target="_blank"&gt;SecureCRT&lt;/a&gt; - 一款支持 SSH、Telnet 等多种协议的终端仿真程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objectivec2swift.com/#/xcode-extension/" target="_blank"&gt;Swiftify&lt;/a&gt; - Xcode ＆ Finder 扩展 Objective-C 转 Swift 代码转换器&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/zqqf16/SYM" target="_blank"&gt;SYM&lt;/a&gt; - 一个图形化的崩溃日志解析工具。 &lt;a href="https://github.com/zqqf16/SYM" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.texstudio.org" target="_blank"&gt;TeXstudio&lt;/a&gt; - 集成创建 LaTeX 文档的写作环境。 &lt;a href="https://sourceforge.net/projects/texstudio/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://u.tools/" target="_blank"&gt;uTools&lt;/a&gt; - 一款基于插件的程序员效率工具，包含非常多的实用插件，如图床、UUID、密码、翻译、JSON格式化等。&lt;/li&gt; &lt;li&gt;&lt;a href="http://vagrantmanager.com" target="_blank"&gt;Vagrant Manager&lt;/a&gt; - 管理你本地服务。&lt;a href="https://github.com/lanayotech/vagrant-manager/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.vagrantup.com" target="_blank"&gt;Vagrant&lt;/a&gt; - 用来构建虚拟开发环境的工具。 &lt;a href="https://github.com/mitchellh/vagrant" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://weflow.io/" target="_blank"&gt;WeFlow&lt;/a&gt; - 一个基于 &lt;a href="https://github.com/Tencent/tmt-workflow" target="_blank"&gt;tmt-workflow&lt;/a&gt; 前端工作流的开发工具。&lt;a href="https://github.com/Tencent/WeFlow" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.woodpeck.cn" target="_blank"&gt;Woodpecker&lt;/a&gt; - 在Mac上查看、编辑iOS App的沙盒文件, UserDefaults, Keychain项&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.zeplin.io/" target="_blank"&gt;zeplin&lt;/a&gt; - 前端与设计协同工作专用工具。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;正则编辑器&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://krillapps.com/patterns/" target="_blank"&gt;Patterns&lt;/a&gt; - 正则表达式编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://motionobj.com/regex/?utm_source=RegexApp&amp;amp;utm_medium=app" target="_blank"&gt;Regex&lt;/a&gt; - 感觉是用过最漂亮的正则表达式测试工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://reggyapp.com/" target="_blank"&gt;Reggy&lt;/a&gt; - 正则表达式编辑器。&lt;a href="https://github.com/samsouder/reggy" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.mactechnologies.com/index.php?page=downloads#regexrx" target="_blank"&gt;RegExRX&lt;/a&gt; - 正则表达式的开发工具。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;API开发和分析&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://mmattozzi.github.io/cocoa-rest-client/" target="_blank"&gt;Cocoa Rest Client&lt;/a&gt; - 比 Postman 看起来漂亮的客户端，测试 HTTP/REST endpoints。&lt;a href="https://github.com/mmattozzi/cocoa-rest-client" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://insomnia.rest/" target="_blank"&gt;Insomnia&lt;/a&gt; - 最直观的跨平台 REST API 客户端。 &lt;a href="https://github.com/getinsomnia/insomnia" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.getpostman.com" target="_blank"&gt;Postman&lt;/a&gt; - Postman 帮助我们快速测试 API。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.katalon.com" target="_blank"&gt;Katalon Studio&lt;/a&gt; - 简单开放性测试前端开放工具， 网页， 手机应用等客户端。 可以使用在不同的浏览器&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;网络分析&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.charlesproxy.com/" target="_blank"&gt;Charles&lt;/a&gt; - 一个代理工具，允许你查看所有的 HTTP 和 HTTPS 流量。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/james-proxy/james" target="_blank"&gt;James&lt;/a&gt; - 用于 https 和 http 进行查询映射请求。 &lt;a href="https://github.com/james-proxy/james" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://mitmproxy.org/" target="_blank"&gt;mitmproxy&lt;/a&gt; - 一款支持 HTTP(S) 的中间人代理工具，可在终端下运行，可用于抓包 &lt;a href="https://github.com/james-proxy/james" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://luckymarmot.com/paw" target="_blank"&gt;Paw&lt;/a&gt; - 先进的 HTTP 客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://proxieapp.com/" target="_blank"&gt;Proxie&lt;/a&gt; - HTTP 调试客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://proxyman.app" target="_blank"&gt;Proxyman&lt;/a&gt; - 适用于 macOS 的现代直观 HTTP 调试代理.&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.wireshark.org" target="_blank"&gt;Wireshark&lt;/a&gt; - 世界上最广泛使用的网络协议分析软件。 &lt;a href="https://github.com/wireshark/wireshark" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;命令行工具&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;A curated list of shell commands and tools specific to OS X.&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/wting/autojump/wiki" target="_blank"&gt;autojump&lt;/a&gt; - 告别又臭又长的路径名，一键直达任何目录。 &lt;a href="https://github.com/wting/autojump" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Bash-it/bash-it" target="_blank"&gt;bash-it&lt;/a&gt; - 一个社区的 bash 的框架。** 开源 **&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/sharkdp/bat" target="_blank"&gt;bat&lt;/a&gt; - 带有语法高亮和Git集成的 &lt;code&gt;cat(1)&lt;/code&gt; 克隆。 &lt;a href="https://github.com/sharkdp/bat" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://swordfishslabs.wordpress.com/2014/09/03/cool-old-term-is-dead-long-live-cool-retro-term/" target="_blank"&gt;color-retro-term&lt;/a&gt; - 一款复古风格的终端，非常酷炫。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Swordfish90/cool-retro-term" target="_blank"&gt;cool-retro-term&lt;/a&gt; - 怀旧的命令行终端。&lt;a href="https://github.com/Swordfish90/cool-retro-term" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.cakebrew.com" target="_blank"&gt;Cakebrew&lt;/a&gt; - &lt;a href="http://brew.sh" target="_blank"&gt;Homebrew&lt;/a&gt; 的客户端软件。摆脱命令方便安装、查看、卸载软件。&lt;a href="https://github.com/brunophilipe/Cakebrew/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://cmus.github.io/" target="_blank"&gt;cmus&lt;/a&gt; - 命令行播放音乐应用。 &lt;a href="https://github.com/cmus" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.getdnote.com/" target="_blank"&gt;Dnote&lt;/a&gt; - 命令行上的笔记本，支持多设备同步和网络界面。 &lt;a href="https://github.com/dnote/dnote" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://fishshell.com/" target="_blank"&gt;Fish Shell&lt;/a&gt; - 智能且用户友好的命令行终端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/nicolargo/glances" target="_blank"&gt;Glances&lt;/a&gt; - 在命令行中查看你系统运行状态的工具。&lt;a href="https://github.com/nicolargo/glances" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://httpie.org" target="_blank"&gt;httpie&lt;/a&gt; - HTTPie 是一个让你微笑的命令行 HTTP 客户端。 &lt;a href="https://github.com/jakubroztocil/httpie" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://hyper.is" target="_blank"&gt;hyper&lt;/a&gt; - 基于 Web 技术的终端，直接替代自带的 Terminal。&lt;a href="https://github.com/zeit/hyper" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://hyperterm.org/" target="_blank"&gt;HyperTerm&lt;/a&gt; - 一款基于 Node 开发的终端软件，逼格很高。&lt;a href="https://github.com/zeit/hyperterm" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.iterm2.com" target="_blank"&gt;iTerm2&lt;/a&gt; - 免费的终端工具，直接替代自带的 Terminal，有非常多惊人的特性。&lt;a href="https://github.com/gnachman/iTerm2" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/mischah/itunes-remote" target="_blank"&gt;itunes-remote&lt;/a&gt; - 通过终端控制您的 iTunes。&lt;a href="https://github.com/mischah/itunes-remote" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/liujianping/job" target="_blank"&gt;job&lt;/a&gt; - 短命令并发、重复执行工具, 适用于压测. &lt;a href="https://github.com/liujianping/job" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://lnav.org" target="_blank"&gt;LNav&lt;/a&gt; - 日志文件阅读器. &lt;a href="https://github.com/tstack/lnav" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/dbcli/mycli" target="_blank"&gt;mycli&lt;/a&gt; - 为 MySQL 命令行客户端，提供语法高亮和提示功能的工具！ &lt;a href="https://github.com/dbcli/mycli" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/rgcr/m-cli" target="_blank"&gt;m-cli&lt;/a&gt; - 用于 macOS 的瑞士军刀。 &lt;a href="https://github.com/rgcr/m-cli" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/guarinogabriel/Mac-CLI" target="_blank"&gt;Mac-CLI&lt;/a&gt; - 自动化您的 OS X 系统的使用。 &lt;a href="https://github.com/guarinogabriel/Mac-CLI" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/mas-cli/mas" target="_blank"&gt;mas&lt;/a&gt; - 一个简单的命令行界面的苹果应用商店。 &lt;a href="https://github.com/mas-cli/mas" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://720kb.github.io/ndm/" target="_blank"&gt;ndm&lt;/a&gt; - 查看本地&lt;a href="http://npmjs.org/" target="_blank"&gt;NPM&lt;/a&gt;安装的包客户端软件。摆脱命令方便安装、查看、卸载软件。&lt;a href="https://github.com/720kb/ndm" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.pgcli.com" target="_blank"&gt;pgcli&lt;/a&gt; - 为Postgres提供一个支持自动补全和语法高亮的命令行工具。 &lt;a href="https://github.com/dbcli/pgcli" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/ggreer/the_silver_searcher" target="_blank"&gt;silver searcher (ag)&lt;/a&gt; - 类似于ack的代码搜索工具，专注于速度。 &lt;a href="https://github.com/ggreer/the_silver_searcher" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.decisivetactics.com/products/serial/" target="_blank"&gt;Serial&lt;/a&gt; - 为工程师和系统管理员嵌入式硬件更容易。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/denysdovhan/spaceship-prompt" target="_blank"&gt;spaceship&lt;/a&gt; - 一个简约，功能强大且极易定制的Zsh提示。 &lt;a href="https://github.com/denysdovhan/spaceship-prompt" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Eugeny/terminus" target="_blank"&gt;Tabby (formerly Terminus)&lt;/a&gt; - 免费的终端工具，基于 Web 技术的终端，用 TypeScript 写成的跨平台终端工具。深受 hyper 启发。 &lt;a href="https://github.com/Eugeny/terminus" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.termius.com/" target="_blank"&gt;Termius&lt;/a&gt; - 免费的终端工具，可以与 windows 平台的 xshell 媲美。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/nvbn/thefuck" target="_blank"&gt;thefuck&lt;/a&gt; - 一个纠正错误命令的工具，输入错误命令后，输入fuck就可以修正成正确的命令行命令，支持自定义的bash_profile命令[ 开源地址]&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/tmux/tmux" target="_blank"&gt;tmux&lt;/a&gt; - 一个优秀的终端复用器类自由软件。&lt;a href="https://github.com/tmux/tmux" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/tmuxinator/tmuxinator" target="_blank"&gt;tmuxinator&lt;/a&gt; - Tmux的配置管理工具。 &lt;a href="https://github.com/tmuxinator/tmuxinator" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/icholy/ttygif" target="_blank"&gt;ttygif&lt;/a&gt; - 将终端录制转换为 GIF 动画。 &lt;a href="https://github.com/icholy/ttygif" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/sindresorhus/trash" target="_blank"&gt;trash&lt;/a&gt; - 将文件和目录移动到废纸篓。 &lt;a href="https://github.com/sindresorhus/trash" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/railsware/upterm" target="_blank"&gt;Upterm&lt;/a&gt; - Upterm (之前是 Black Screen) 来自 21 世纪的强大终端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.zsh.org" target="_blank"&gt;Zsh&lt;/a&gt; - 一个专为交互式使用而设计的命令行 shell。 &lt;a href="https://sourceforge.net/projects/zsh/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;版本控制&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://git-scm.com/" target="_blank"&gt;Git&lt;/a&gt; - 版本控制工具，官网提供&lt;a href="https://git-scm.com/download/gui/mac" target="_blank"&gt;数十种 GUI 客户端&lt;/a&gt; for Mac。&lt;a href="https://github.com/git/git" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://subversion.apache.org/" target="_blank"&gt;SVN&lt;/a&gt; - 版本控制工具。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;GUI&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.zennaware.com/cornerstone/" target="_blank"&gt;Cornerstone&lt;/a&gt; - Mac 上最佳的 SVN 管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://git-fork.com/" target="_blank"&gt;Fork&lt;/a&gt; - 一个快速友好的 Git 客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gitfinder.com/" target="_blank"&gt;GitFinder&lt;/a&gt; - 一个快速和轻量级的 Git 客户端的 Mac 与 Finder 集成。&lt;/li&gt; &lt;li&gt;&lt;a href="https://rowanj.github.io/gitx/" target="_blank"&gt;GitX&lt;/a&gt; - &lt;a href="https://github.com/pieter/gitx" target="_blank"&gt;Pieter's&lt;/a&gt;的衍生版本，维护增强生产力和团队开发变化。 &lt;a href="https://github.com/rowanj/gitx" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Shikkic/gitbar" target="_blank"&gt;Gitbar&lt;/a&gt; - 开源，在你的菜单栏上显示 GitHub 贡献统计。&lt;a href="https://github.com/Shikkic/gitbar" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://desktop.github.com/" target="_blank"&gt;GitHub Desktop&lt;/a&gt; - 使用 GitHub 的 GUI 应用。&lt;/li&gt; &lt;li&gt;&lt;a href="http://gitup.co/" target="_blank"&gt;GitUp&lt;/a&gt; - 一个简单功能强大的 Git 客户端。&lt;a href="https://github.com/git-up/GitUp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.gitkraken.com/" target="_blank"&gt;GitKraken&lt;/a&gt; - 最流行的图形用户界面的 git 管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://hub.github.com/" target="_blank"&gt;Hub&lt;/a&gt; - 将 GitHub 接口和 Git 命令进行包装。&lt;a href="https://github.com/github/hub" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://ohmystarapp.com/" target="_blank"&gt;OhMyStar&lt;/a&gt; 最好的组织 Github Star 的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.sourcetreeapp.com/" target="_blank"&gt;SourceTree&lt;/a&gt; - 强大的 Git 跨平台客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.syntevo.com/smartgit/" target="_blank"&gt;SmartGit&lt;/a&gt; - 非商业用途免费，全平台支持，集成 Github 服务。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.sublimemerge.com/" target="_blank"&gt;Sublime Merge&lt;/a&gt; -  Git客户端，来自Sublime Text的制造商。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.git-tower.com/" target="_blank"&gt;Tower2&lt;/a&gt; - 最强大的 Git 客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.versionsapp.com/" target="_blank"&gt;Versions&lt;/a&gt; - Mac 上最好的 SVN 管理工具。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;版本控制系统&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://coding.net" target="_blank"&gt;Coding.net&lt;/a&gt; - 代码托管，项目管理，WebIDE，演示部署，开启云端开发模式，让开发更简单。&lt;/li&gt; &lt;li&gt;&lt;a href="http://gitlab.com/" target="_blank"&gt;GitLab&lt;/a&gt; - 一个用于仓库管理系统的开源项目。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com" target="_blank"&gt;GitHub&lt;/a&gt; GitHub 托管代码，项目管理，演示部署，瞧，您现在就在访问GitHub。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gogs.io" target="_blank"&gt;Gogs&lt;/a&gt; - 一款极易搭建的自助 Git 服务 Golang 版本。&lt;a href="https://github.com/gogits/gogs" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.gerritcodereview.com" target="_blank"&gt;Gerrit&lt;/a&gt; Gerrit 是一个免费、开放源代码的代码审查软件，使用网页界面。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.gitblit.com/" target="_blank"&gt;Gitblit&lt;/a&gt; Java 版本 Git 代码托管，项目管理。&lt;a href="https://github.com/gitblit/gitblit" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://gitea.io" target="_blank"&gt;Gitea&lt;/a&gt; - Gogs 的 fork 版本。** 开源 **&lt;/li&gt; &lt;li&gt;&lt;a href="https://phabricator.com" target="_blank"&gt;phabricator&lt;/a&gt; phabricator 支持 Git、SVN、HG 基于 PHP + Mysql 的开放源代码软件开发平台。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;数据库&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/qishibo/AnotherRedisDesktopManager" target="_blank"&gt;Another Redis Desktop Manager&lt;/a&gt; - 一款稳定全新的Redis管理工具。** 开源 **&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/bdash-app/bdash" target="_blank"&gt;Bdash&lt;/a&gt; - SQL 客户端应用程序，支持 MySQL、 PostgreSQL (Redshift)、 BigQuery。&lt;a href="https://github.com/bdash-app/bdash" target="_blank"&gt;** 开源 ** &lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://menial.co.uk/base/" target="_blank"&gt;Base 2&lt;/a&gt; - 一个用于管理 SQLite 数据库的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.eisbahn.jp/chrome_mysql_admin" target="_blank"&gt;Chrome MySQL Admin&lt;/a&gt; - 一个 Chrome 插件，是 MySQL 开发的跨平台、可视化数据库工具。&lt;a href="https://github.com/yoichiro/chrome_mysql_admin" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/ChristianKienle/Core-Data-Editor" target="_blank"&gt;Core Data Editor&lt;/a&gt; - 核心数据编辑器可让您轻松查看，编辑和分析应用程序的数据。 &lt;a href="https://github.com/luin/medis" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://sqlitebrowser.org/" target="_blank"&gt;DB Browser for SQLite&lt;/a&gt; - 一个跨平台的用于管理 SQLite 数据库的软件。 &lt;a href="https://github.com/sqlitebrowser/sqlitebrowser" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.jetbrains.com/datagrip/" target="_blank"&gt;DataGrip&lt;/a&gt; - JetBrains 公司旗下一款数据库管理工具。&lt;a href="https://www.jetbrains.com/student/" target="_blank"&gt;点击这里&lt;/a&gt; &lt;strong&gt;学生&lt;/strong&gt;免费。&lt;/li&gt; &lt;li&gt;&lt;a href="https://dbeaver.jkiss.org/" target="_blank"&gt;DBeaver&lt;/a&gt; - 跨平台 SQL 客户端，支持大部分主流数据库&lt;/li&gt; &lt;li&gt;&lt;a href="http://garrylachman.github.io/ElectroCRUD/" target="_blank"&gt;ElectroCRUD&lt;/a&gt; - MySQL 数据库 CRUD 应用程序。&lt;a href="https://github.com/garrylachman/ElectroCRUD" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.sequelpro.com/" target="_blank"&gt;Sequel Pro&lt;/a&gt; - 一个 MySQL 数据库管理软件。&lt;a href="https://github.com/sequelpro/sequelpro" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jackdb.com/" target="_blank"&gt;JackDB&lt;/a&gt; - 直接的 SQL 访问你所有的数据，无论在哪里。&lt;a href="https://github.com/yoichiro/chrome_mysql_admin" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://getmedis.com" target="_blank"&gt;medis&lt;/a&gt; - 漂亮的 Redis 管理软件。&lt;a href="https://github.com/luin/medis" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mongodb.com" target="_blank"&gt;MongoDB&lt;/a&gt; - 一个基于分布式文件存储的数据库。 &lt;a href="https://github.com/gcollazo/mongodbapp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;a href="https://github.com/ramnes/awesome-mongodb#desktop" target="_blank"&gt;&lt;img src="https://camo.githubusercontent.com/491b767d4b8fe216d87d95e28b52a53d86683974780fb6110b0cc225edf6e2d8/68747470733a2f2f6a617977636a6c6f76652e6769746875622e696f2f73622f69636f2f6d696e2d617765736f6d652e737667" alt="Awesome List" title="Awesome List" /&gt;&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.mongobooster.com/" target="_blank"&gt;MongoBooster&lt;/a&gt; - MongoDB 图形化管理软件，内嵌 MongoShell，ES6 语法，流畅查询及智能感知。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gcollazo.github.io/mongodbapp/" target="_blank"&gt;mongoDB.app&lt;/a&gt; - 在Mac 上最简单的使用 MongoDB。&lt;a href="https://github.com/gcollazo/mongodbapp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.litixsoft.de/english/mms/" target="_blank"&gt;Mongo Management Studio&lt;/a&gt; - MongoDB 图形化客户端管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.macexplorer.co/en/mdb-explorer.php" target="_blank"&gt;MDB Explorer&lt;/a&gt; - Mac 上查看编辑 Access 数据库的工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://dev.mysql.com/downloads/workbench/" target="_blank"&gt;MySQL Workbench&lt;/a&gt; - MySQL 数据库官方管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.navicat.com.cn/products/navicat-data-modeler" target="_blank"&gt;Navicat Data Modeler&lt;/a&gt; - 一个数据库设计工具，它帮助创建高质素的概念、逻辑和物理数据模型。&lt;/li&gt; &lt;li&gt;&lt;a href="https://eggerapps.at/postico/" target="_blank"&gt;Postico&lt;/a&gt; - 现代 PostgreSQL 客户端，漂亮功能多。&lt;/li&gt; &lt;li&gt;&lt;a href="http://postgresapp.com/" target="_blank"&gt;Postgres.app&lt;/a&gt; - Mac 上最简单的方法的使用 PostgreSQL 关系型数据库管理系统。&lt;a href="https://github.com/PostgresApp/PostgresApp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.psequel.com/" target="_blank"&gt;PSequel&lt;/a&gt; - PostgreSQL 数据库 GUI 软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://pgmodeler.io" target="_blank"&gt;pgModeler&lt;/a&gt; - 是一个专为PostgreSQL设计的开源数据建模工具。 &lt;a href="https://github.com/pgmodeler/pgmodeler" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/UUGU/redis-client-app" target="_blank"&gt;RedisClient&lt;/a&gt; - 漂亮跨平台的Redis管理软件。&lt;a href="https://github.com/UUGU/redis-client-app" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://redisdesktop.com/" target="_blank"&gt;RedisDesktopManager&lt;/a&gt; - Redis 跨平台的 GUI 管理工具。&lt;a href="https://github.com/uglide/RedisDesktopManager" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.sqlprostudio.com/" target="_blank"&gt;SQLPro Studio&lt;/a&gt; - 支持 SQL Server, Postgres, Oracle 以及 MySQL 等主流的数据库可视化管理工具.&lt;/li&gt; &lt;li&gt;&lt;a href="https://aurvan.com/sqlight/" target="_blank"&gt;SQLight&lt;/a&gt; - 一个 SQLite 数据库管理器工具，非常好用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://tableplus.io" target="_blank"&gt;TablePlus&lt;/a&gt; - 支持 PostgreSQL，MySQL，RedShift，MariaDB... 各种数据库的高颜值客户端。 &lt;a href="https://github.com/TablePlus/TablePlus" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://public.tableau.com/s/" target="_blank"&gt;Tableau Public&lt;/a&gt; - 数据可视化工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://protonail.com" target="_blank"&gt;Keylord&lt;/a&gt; - Redis，Bolt，LevelDB 和 Memcached 键值数据库的桌面GUI客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/cmushroom/redis-pro" target="_blank"&gt;redis-pro&lt;/a&gt; - 轻量，易用的 Redis 客户端管理工具，使用SwiftUI编写，很好的支持 Dark mode。 &lt;a href="https://github.com/cmushroom/redis-pro" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;设计和产品&lt;/h2&gt; &lt;h3&gt;设计工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://secure.flyingmeat.com/acorn/" target="_blank"&gt;Acorn&lt;/a&gt; - 一个像 PS，全面的功能集的图像编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://affinity.serif.com/en-us/designer/" target="_blank"&gt;Affinity Designer&lt;/a&gt; - 矢量图像设计工具，可以是 Adobe Illustrator 的替代。&lt;/li&gt; &lt;li&gt;&lt;a href="https://affinity.serif.com/en-us/photo/" target="_blank"&gt;Affinity Photo&lt;/a&gt; - 光栅图像设计工具，可以替代 Adobe PS 图象处理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://al.chemy.org/" target="_blank"&gt;Alchemy&lt;/a&gt; - 开源的绘图工具软件，用于素描、会话以及一种新的绘图方式。&lt;a href="http://svn.al.chemy.org/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.belightsoft.com/art-text/" target="_blank"&gt;Art Text 3&lt;/a&gt; - 生成各种特效字体。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.blender.org/" target="_blank"&gt;Blender&lt;/a&gt; - 全功能可扩展的跨平台 3D 内容套件。&lt;a href="https://developer.blender.org/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.figma.com/" target="_blank"&gt;Figma&lt;/a&gt; - 一款基于 Web 的实时协作的云设计软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://fontforge.github.io/" target="_blank"&gt;FontForge&lt;/a&gt; - 字体编辑工具。 &lt;a href="https://github.com/fontforge" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.gimp.org" target="_blank"&gt;GIMP&lt;/a&gt; - 图像编辑软件，号称 Linux 下的 PhotoShop，同时有 Mac 版本。&lt;a href="https://www.gimp.org/source/#gimp-source-code" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://designer.io/" target="_blank"&gt;Gravit Designer&lt;/a&gt; - 混合矢量/位图布局应用，比起 Sketch 还差一点。&lt;/li&gt; &lt;li&gt;&lt;a href="https://tenonedesign.com/inklet.php" target="_blank"&gt;inklet&lt;/a&gt; - 将 Mac 上的触摸板变成绘图板。&lt;/li&gt; &lt;li&gt;&lt;a href="https://inkscape.org/zh/" target="_blank"&gt;Inkscape&lt;/a&gt; - 一款开源矢量图形编辑软件，与 Illustrator、Freehand、CorelDraw、Xara X 等其他软件相似。&lt;a href="https://launchpad.net/inkscape" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://krita.org/" target="_blank"&gt;Krita&lt;/a&gt; - 一个开源的位图形编辑软件，包含一个绘画程式和照片编辑器。 &lt;a href="https://github.com/KDE/krita" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://helftone.com/" target="_blank"&gt;Monodraw&lt;/a&gt; -  macOS 平台上强大的  ASCII 设计流程编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://ephtracy.github.io/" target="_blank"&gt;MagicaVoxel&lt;/a&gt; - 轻量级的8位像素编辑和交互路径追踪渲染器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.makehumancommunity.org" target="_blank"&gt;MakeHuman&lt;/a&gt; - 功能强大且免费的3D人体建模器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://nikcollection.dxo.com/" target="_blank"&gt;Nik Collection&lt;/a&gt; - 专业照片后期制作工具，Google 收购后免费&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.pixelmator.com/mac/" target="_blank"&gt;Pixelmator&lt;/a&gt; - 强大的图像编辑器，可能PS图像处理软件的选择。&lt;/li&gt; &lt;li&gt;&lt;a href="http://paintbrush.sourceforge.net/" target="_blank"&gt;Paintbrush&lt;/a&gt; - 位图图像编辑器。&lt;a href="https://sourceforge.net/projects/paintbrush/files/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.pencil2d.org" target="_blank"&gt;Pencil2D&lt;/a&gt; - 制作2D手绘动画的简单直观的工具。 &lt;a href="https://github.com/pencil2d/pencil" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://principleformac.com/" target="_blank"&gt;Principle&lt;/a&gt; -  使用它很容易设计动画和交互式用户界面。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pixelperfect-app.com/" target="_blank"&gt;Pixel Perfect&lt;/a&gt; - 比较 UI 模型和开发结果非常容易。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pixologic.com/sculptris/#" target="_blank"&gt;Sculptris&lt;/a&gt; - 所见所得的 3D 建模。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.sketchapp.com/" target="_blank"&gt;Sketch&lt;/a&gt; - 混合矢量/位图布局应用，特别适用于用户界面，Web 和移动设计。 &lt;ul&gt; &lt;li&gt;&lt;a href="http://sketchtoolbox.com/" target="_blank"&gt;Sketch Toolbox&lt;/a&gt; - 一个超级简单的 Sketch 插件管理器。&lt;a href="https://github.com/buzzfeed/Sketch-Toolbox" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://utom.design/measure/" target="_blank"&gt;Measure&lt;/a&gt; - 设计稿标注、测量工具。 &lt;a href="https://github.com/utom/sketch-measure" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://abynim.github.io/UserFlows/" target="_blank"&gt;User Flows&lt;/a&gt; - 直接从画板生成流程图。 &lt;a href="https://github.com/abynim/UserFlows" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;&lt;a href="https://yo-op.github.io/sketchcachecleaner/" target="_blank"&gt;Sketch Cache Cleaner&lt;/a&gt; - 清理 Sketch 历史文件，释放磁盘空间。 &lt;a href="https://github.com/yo-op/sketchcachecleaner" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.sketchbook.com/?locale=cn" target="_blank"&gt;SketchBook&lt;/a&gt; - 出众的绘图软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/duyquoc/ScreenToLayers" target="_blank"&gt;ScreenToLayers&lt;/a&gt; - 轻松导出桌面分层文件 PSD 文件。&lt;a href="https://github.com/duyquoc/ScreenToLayers" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://sparkleapp.com/" target="_blank"&gt;Sparkle&lt;/a&gt; - 可视化网页设计工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.tayasui.com/sketches/" target="_blank"&gt;Tayasui Sketches&lt;/a&gt; - 专业的绘图软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://vectr.com/" target="_blank"&gt;Vectr&lt;/a&gt; - 免费图形编辑器。这是一个简单而强大的 Web 和桌面跨平台工具，把你的设计变成现实。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;原型流程&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.axure.com/" target="_blank"&gt;Axure RP 8&lt;/a&gt; - 画原型图工具，团队协作，方便好用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.protopie.io/" target="_blank"&gt;ProtoPie&lt;/a&gt; - 高保真交互原型设计。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.adobe.com/products/experience-design.html" target="_blank"&gt;Adobe XD (Experience Design)&lt;/a&gt; - 用于网站和移动应用的设计和原型设计。&lt;/li&gt; &lt;li&gt;&lt;a href="https://balsamiq.com/products/mockups/" target="_blank"&gt;Balsamiq Mockups&lt;/a&gt; - 一个快速的网页设计原型工具，帮助你更快、更聪明的工作。&lt;/li&gt; &lt;li&gt;&lt;a href="http://origami.design/" target="_blank"&gt;Origami Studio&lt;/a&gt; - 一种设计现代界面的新工具，由 Facebook 设计师构建和使用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.flinto.com/" target="_blank"&gt;Flinto&lt;/a&gt; - 快速制作高保真的互交原型工具，支持 Sketch 导入。&lt;/li&gt; &lt;li&gt;&lt;a href="https://kiteapp.co/" target="_blank"&gt;Kite&lt;/a&gt; - 一个强大的动画制作工具制作 Mac 和 iOS 原型中的应用。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.justinmind.com" target="_blank"&gt;Justinmind&lt;/a&gt; - 功能更丰富团队协作方便。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mockflow.com" target="_blank"&gt;MockFlow&lt;/a&gt; - 用于网页设计和可用性测试的在线原型设计套件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pencil.evolus.vn/" target="_blank"&gt;pencil&lt;/a&gt; - 开源免费制作软件原型的工具 &lt;a href="https://github.com/evolus/pencil" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.mockplus.com" target="_blank"&gt;Mockplus&lt;/a&gt; - 更快更简单的原型设计工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/omnigraffle" target="_blank"&gt;OmniGraffle&lt;/a&gt; - 可用来绘制图表、流程图、组织结构图、思维导图以及插图或原型。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.xmind.net" target="_blank"&gt;XMind&lt;/a&gt; - 一款实用的思维导图软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://lighten.xmind.net/" target="_blank"&gt;Lighten&lt;/a&gt; - XMind 出品的一款实用的思维导图软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://loremify.com" target="_blank"&gt;Loremify&lt;/a&gt; - 快速准确的设计，原型或生成标题，段落，列表和文章。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.literatureandlatte.com/scapple.php" target="_blank"&gt;Scapple&lt;/a&gt; - 一款实用的思维导图软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://framer.com/" target="_blank"&gt;Framer&lt;/a&gt; - 做交互原型的工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://marvelapp.com/" target="_blank"&gt;Marvel&lt;/a&gt; - 简单设计，原型设计和协作。&lt;/li&gt; &lt;li&gt;&lt;a href="http://mindnode.com/" target="_blank"&gt;MindNode&lt;/a&gt; - 简洁的风格与人性化的操作，绘制思维脑图。&lt;/li&gt; &lt;li&gt;&lt;a href="https://writemapper.com/" target="_blank"&gt;WriteMapper&lt;/a&gt; - 专为写作者而设的脑图工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://simplemind.eu/" target="_blank"&gt;SimpleMind&lt;/a&gt; - 超小体积的思维导图工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macsvg.org/" target="_blank"&gt;macSVG&lt;/a&gt; - 设计 HTML5 SVG 和动画. &lt;a href="https://github.com/dsward2/macSVG" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;作图工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.draw.io/" target="_blank"&gt;Draw.io&lt;/a&gt; - 上百种图形，支持多种格式导出。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/omnigraffle/" target="_blank"&gt;OmniGraffle&lt;/a&gt; - Omni 成员，native 应用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.processon.com/" target="_blank"&gt;ProcessOn&lt;/a&gt; - 流程图、思维导图、原型图... 中文友好，免费保存 5 个文件。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;截图工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/onmyway133/GifCapture" target="_blank"&gt;GifCapture&lt;/a&gt; - 开源 macOS 截屏生成 Gif 工具。&lt;a href="https://github.com/onmyway133/GifCapture" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://gifox.io/" target="_blank"&gt;Gifox&lt;/a&gt; - 专业的高颜值 GIF 录制应用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gfycat.com/gifbrewery" target="_blank"&gt;GIF Brewery&lt;/a&gt; - gives everyone the power to create stunning GIFs from video files.&lt;/li&gt; &lt;li&gt;&lt;a href="https://giphy.com/apps/giphycapture" target="_blank"&gt;GIPHY Capture&lt;/a&gt; - 免费软件的捕捉和分享图片在桌面上。&lt;/li&gt; &lt;li&gt;&lt;a href="https://getkap.co/" target="_blank"&gt;Kap&lt;/a&gt; - 轻量 GIF 录屏小工具。&lt;a href="https://github.com/wulkano/kap" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/keycastr/keycastr" target="_blank"&gt;KeyCastr&lt;/a&gt; - 录屏好帮手，实时显示按键操作的小工具。&lt;a href="https://github.com/keycastr/keycastr" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.cockos.com/licecap/" target="_blank"&gt;Licecap&lt;/a&gt; - 是一款屏幕录制工具输出 GIF，录制过程中可以随意改变录屏范围。&lt;a href="https://github.com/justinfrankel/licecap" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://monosnap.com/" target="_blank"&gt;Monosnap&lt;/a&gt; - 制作截图，录制视频共享文件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://evernote.com/intl/zh-cn/skitch/" target="_blank"&gt;Skitch&lt;/a&gt; - 截图附带强大的标注功能。&lt;/li&gt; &lt;li&gt;&lt;a href="http://shifty.natethompson.io" target="_blank"&gt;Shifty&lt;/a&gt; - 一个菜单栏应用程序，让您更多地控制夜班。 &lt;a href="https://github.com/thompsonate/Shifty" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://txtlabs.com/" target="_blank"&gt;ScreenShot PSD&lt;/a&gt; - 将屏幕捕获存为分层的 PSD，便于编辑。&lt;/li&gt; &lt;li&gt;&lt;a href="https://zh.snipaste.com/" target="_blank"&gt;Snipaste&lt;/a&gt; - 一个简单但强大的截图工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://snip.qq.com/" target="_blank"&gt;Snip&lt;/a&gt; - 高效的截图工具，支持滚动截屏，腾讯作品。&lt;/li&gt; &lt;li&gt;&lt;a href="http://teampaper.me/snap/" target="_blank"&gt;Teampaper Snap&lt;/a&gt; - 为设计师量身定做的屏幕截图兼注释工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://jietu.qq.com/" target="_blank"&gt;截图(Jietu)&lt;/a&gt; - 截图附带强大的标注功能，腾讯作品。&lt;/li&gt; &lt;li&gt;&lt;a href="http://xnipapp.com/" target="_blank"&gt;Xnip&lt;/a&gt; - 免费好用的滚动截屏利器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.better365.cn/" target="_blank"&gt;iShot&lt;/a&gt; - 完全免费、功能全面的截图工具，支持贴图、滚动截图、延时截图等。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;其它工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/mancunianetz/APNGb" target="_blank"&gt;APNGb&lt;/a&gt; - 编辑 png 图片格式的软件。 &lt;a href="https://github.com/mancunianetz/APNGb" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://assetizr.com" target="_blank"&gt;Assetizr&lt;/a&gt; - 图片编辑应用，轻松更改图片尺寸，压缩图片，重命名图片。&lt;/li&gt; &lt;li&gt;&lt;a href="https://itunes.apple.com/cn/app/shotbuilder/id1294179975?mt=12" target="_blank"&gt;AppIconBuilder(图标构建)&lt;/a&gt; - App图标多平台一键导出。&lt;/li&gt; &lt;li&gt;&lt;a href="http://couleursapp.com" target="_blank"&gt;Couleurs&lt;/a&gt; - 简单的屏幕取色应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://cn.eagle.cool/" target="_blank"&gt;Eagle App&lt;/a&gt; - 强大的图片、视频、音频、設計素材及文件管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://jumpzero.com/frank/" target="_blank"&gt;Frank DeLoupe&lt;/a&gt; - 支持 Retina 的屏幕拾色器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.img2icnsapp.com" target="_blank"&gt;Image2icon&lt;/a&gt; - 将你的图片转换成图标。&lt;/li&gt; &lt;li&gt;&lt;a href="https://pngmini.com/" target="_blank"&gt;ImageAlpha&lt;/a&gt; - 压缩 PNG 图片，去掉无效的透明。&lt;a href="https://github.com/pornel/ImageAlpha" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://imageoptim.com/mac" target="_blank"&gt;ImageOptim&lt;/a&gt; - 压缩图片，删除 EXIF 信息。&lt;a href="https://github.com/ImageOptim/ImageOptim" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://toolinbox.net/iPic/" target="_blank"&gt;iPic&lt;/a&gt; - 上传图片至七牛、阿里云等图床，支持 Markdown 链接。&lt;/li&gt; &lt;li&gt;&lt;a href="http://appersian.net/" target="_blank"&gt;IconKit&lt;/a&gt; - App图标自动生成器。 (https://itunes.apple.com/cn/app/iconkit-icon-resizer-for-app/id507135296?mt=12)&lt;/li&gt; &lt;li&gt;&lt;a href="http://geticonjar.com/" target="_blank"&gt;Iconjar&lt;/a&gt; - 图标管理软件，带组织和搜索功能。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.jpegmini.com/" target="_blank"&gt;JPEGmini&lt;/a&gt; - 将图像尺寸降低高达 80％，而不会影响质量。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.presetbrewery.com" target="_blank"&gt;Preset Brewery&lt;/a&gt; - 将Lightroom预设转换为Adobe Camera Raw的工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Molunerfinn/PicGo" target="_blank"&gt;PicGo&lt;/a&gt; - 支持常用 cdn 的图床工具。&lt;a href="https://github.com/Molunerfinn/PicGo" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.boltnev.com/resizemaster/" target="_blank"&gt;Resize Master&lt;/a&gt; - 更快速和容易批量调整图像和加水印。&lt;/li&gt; &lt;li&gt;&lt;a href="http://rightfontapp.com/" target="_blank"&gt;RightFont&lt;/a&gt; - 字体管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.svgs.us/" target="_blank"&gt;svgus&lt;/a&gt; - SVG 图片管理器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://ethanschoonover.com/solarized" target="_blank"&gt;Solarized&lt;/a&gt; - 干净清爽的颜色主题，支持 iTerm、Intellij IDEA、Vim 等。&lt;/li&gt; &lt;li&gt;&lt;a href="http://theolabrothers.com/" target="_blank"&gt;Sip&lt;/a&gt; - 收集，整理和分享你的颜色拾色器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.eigenlogik.com/spectrum/mac" target="_blank"&gt;Spectrum&lt;/a&gt; - 一款可以轻松直观地创建漂亮配色方案的应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/kyleduo/TinyPNG4Mac" target="_blank"&gt;TinyPNG4Mac&lt;/a&gt; - 图片压缩专用开源工具。&lt;a href="https://github.com/kyleduo/TinyPNG4Mac" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://tropy.org/" target="_blank"&gt;Tropy&lt;/a&gt; - 照片档案管理工具。&lt;a href="https://github.com/tropy/tropy" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/gee1k/uPic" target="_blank"&gt;uPic&lt;/a&gt; - macOS 原生应用，功能强大且简洁的图床客户端。 &lt;a href="https://github.com/gee1k/uPic" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://getmarkman.com/" target="_blank"&gt;马克鳗&lt;/a&gt; - 高效的设计稿标注、测量工具。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;虚拟机&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.docker.com/" target="_blank"&gt;Docker&lt;/a&gt; - 开源的应用容器引擎。 &lt;a href="https://github.com/docker" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;a href="https://github.com/veggiemonk/awesome-docker#readme" target="_blank"&gt;&lt;img src="https://camo.githubusercontent.com/491b767d4b8fe216d87d95e28b52a53d86683974780fb6110b0cc225edf6e2d8/68747470733a2f2f6a617977636a6c6f76652e6769746875622e696f2f73622f69636f2f6d696e2d617765736f6d652e737667" alt="Awesome List" title="Awesome List" /&gt;&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://dockstation.io/" target="_blank"&gt;DockStation&lt;/a&gt; - 管理 Docker 项目的程序。 &lt;a href="https://github.com/DockStation/dockstation" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.parallels.com/" target="_blank"&gt;Parallels Desktop&lt;/a&gt; - 虽然好用但是收费机制，更新花钱、花钱、花钱。&lt;/li&gt; &lt;li&gt;&lt;a href="https://portainer.io/" target="_blank"&gt;Portainer&lt;/a&gt; - 基于网页管理 Docker 容器和 swarm 集群。 &lt;a href="https://github.com/portainer/portainer" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.virtualbox.org" target="_blank"&gt;Virtual Box&lt;/a&gt; - 免费、免费、免费，带 NTFS 读写，不用买 ParagonNTFS，省100块。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.vmware.com/" target="_blank"&gt;VMware Fusion&lt;/a&gt; - 强大的虚拟机，商业软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://veertu.com" target="_blank"&gt;Veertu&lt;/a&gt; - Mac 上轻量级的虚拟机。通过一种高响应，沙箱且本地化的方式在你在 Mac 上运行虚拟机。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;通信&lt;/h2&gt; &lt;p&gt;&lt;em&gt;推荐一些通信工具，沟通，团队协同。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://adium.im/" target="_blank"&gt;Adium&lt;/a&gt; - 呃，这个是老的集成多个平台的聊天客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://bearychat.com/" target="_blank"&gt;BearyChat&lt;/a&gt; - 互联网团队协作，沟通工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/stonesam92/ChitChat" target="_blank"&gt;ChitChat&lt;/a&gt; - WhatsApp 非官方。&lt;a href="https://github.com/stonesam92/ChitChat" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/geeeeeeeeek/electronic-wechat" target="_blank"&gt;Electronic WeChat&lt;/a&gt; - 调用微信接口，使用 &lt;a href="https://github.com/electron/electron" target="_blank"&gt;Electron&lt;/a&gt; 开发的第三方漂亮开源微信应用。&lt;a href="https://github.com/geeeeeeeeek/electronic-wechat" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://meetfranz.com/" target="_blank"&gt;Franz&lt;/a&gt; - 一个使用 &lt;a href="https://electronjs.org/" target="_blank"&gt;Electron&lt;/a&gt;开发的，可以同时登录 23 个平台的即时通讯软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://flumeapp.com" target="_blank"&gt;Flume&lt;/a&gt; - 简约大气高逼格的Instagram，如果只是浏览点赞评论，免费版已经足够用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gitter.im" target="_blank"&gt;Gitter&lt;/a&gt; - 关于 GitHub 的项目交流，支持 Markdown，对开发者极为友好。&lt;/li&gt; &lt;li&gt;&lt;a href="https://keybase.io/" target="_blank"&gt;Keybase&lt;/a&gt; - 一个安全的消息应用程序! &lt;a href="https://github.com/keybase" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://weiboformac.sinaapp.com/" target="_blank"&gt;Maipo脉搏&lt;/a&gt; - 微博第三方 Mac 应用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://fbmacmessenger.rsms.me/" target="_blank"&gt;Messenger&lt;/a&gt; - Facebook 第三方聊天工具。&lt;a href="https://github.com/rsms/fb-mac-messenger" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://im.qq.com/macqq/index.shtml" target="_blank"&gt;QQ&lt;/a&gt; - QQ for Mac App。&lt;/li&gt; &lt;li&gt;&lt;a href="http://rambox.pro/" target="_blank"&gt;Rambox&lt;/a&gt; - 消息和电子邮件应用程序，将常见的Web应用程序组合成一个程序。 &lt;a href="https://github.com/saenzramiro/rambox" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.skype.com/" target="_blank"&gt;Skype&lt;/a&gt; - Skype 共享、跨平台的短信和电话。&lt;/li&gt; &lt;li&gt;&lt;a href="https://slack.com/" target="_blank"&gt;Slack&lt;/a&gt; - 团队协作，沟通工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://desktop.telegram.org" target="_blank"&gt;Telegram&lt;/a&gt; - 通讯新时代。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.codeux.com/textual" target="_blank"&gt;Textual&lt;/a&gt; - 最受欢迎的世界与我们相关的 KPI 应用 for OS X。&lt;a href="https://github.com/Codeux-Software/Textual" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.teambition.com" target="_blank"&gt;Teambition&lt;/a&gt; - 团队协作。提供管理任务、安排日程、查找文件、即时讨论等团队所需要的一切协作功能。&lt;/li&gt; &lt;li&gt;&lt;a href="http://weixin.qq.com/cgi-bin/readtemplate?t=mac&amp;amp;platform=wx&amp;amp;lang=zh_CN" target="_blank"&gt;WeChat&lt;/a&gt; - 微信 for Mac App。&lt;/li&gt; &lt;li&gt;&lt;a href="https://weechat.org/" target="_blank"&gt;WeeChat&lt;/a&gt; - 一个命令行聊天客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://zoom.us/" target="_blank"&gt;Zoom&lt;/a&gt; - 视频会议 &amp;amp; 屏幕共享，提供录制功能。&lt;/li&gt; &lt;li&gt;&lt;a href="https://imach.me/gohanapp/" target="_blank"&gt;御飯&lt;/a&gt; - 饭否第三方Mac应用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/jianliaoim/talk-os" target="_blank"&gt;简聊&lt;/a&gt; - 企业级即时沟通工具，已经下线了，可以自己搭建一套系统玩儿。&lt;a href="https://github.com/jianliaoim/talk-os" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.dingtalk.com/index-b.html#download_block" target="_blank"&gt;钉钉&lt;/a&gt; - 企业级办公通讯免费平台。&lt;/li&gt; &lt;li&gt;&lt;a href="https://pubu.im/apps/osx" target="_blank"&gt;零信&lt;/a&gt; - 随时随地工作，跨平台。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.jingoal.com/client/mac/mac.htm" target="_blank"&gt;今目标&lt;/a&gt; - 一款面向中小企业的互联网工作平台。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.rishiqing.com" target="_blank"&gt;日事清&lt;/a&gt; - 工作计划软件，日志软件，项目管理，团队协作软件，个人日程管理，团队协作工具。日程安排，计划分配，笔记总结等。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.rtxapp.com/mac" target="_blank"&gt;RTX_腾讯通&lt;/a&gt; - 企业内部可以使用的聊天软件，企业内部可以使用此通讯工具，这个软件有Mac版本也有win版本，Mac版本专为 Retina 显示优化过&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Email&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://airmailapp.com" target="_blank"&gt;Airmail&lt;/a&gt; - 快速的邮件客户端支持 Mac 和 iPhone。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.foxmail.com/mac/" target="_blank"&gt;Foxmail&lt;/a&gt; - 快速的邮件客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="http://mail.163.com/dashi/" target="_blank"&gt;网易邮箱大师&lt;/a&gt; - 全平台的邮箱管理客户端，网易邮箱大师电脑版。&lt;/li&gt; &lt;li&gt;&lt;a href="https://smallcubed.com/mt/" target="_blank"&gt;MailTags&lt;/a&gt; - 管理和组织邮件，日程和标签进行分类邮件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://nylas.com/nylas-mail/" target="_blank"&gt;Nylas Mail&lt;/a&gt; - 免费邮件客户端。  &lt;a href="https://github.com/nylas/nylas-mail" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.nylas.com/" target="_blank"&gt;N1&lt;/a&gt; - 可以扩展的开源收费邮件客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://newtonhq.com" target="_blank"&gt;Newton(原Cloudmagic)&lt;/a&gt; - 界面非常简洁的一个邮件客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.postbox-inc.com" target="_blank"&gt;Postbox&lt;/a&gt; - 这个貌似也非常强大哦，关键是简洁漂亮的收费邮件客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://polymail.io/" target="_blank"&gt;Polymail&lt;/a&gt; - 简单，功能强大，长得好看的新晋邮件客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://sparkmailapp.com/" target="_blank"&gt;Spark&lt;/a&gt; - 新推出的快速邮件客户端支持 Mac 和 iPhone。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mozilla.org/zh-CN/thunderbird/" target="_blank"&gt;ThunderBird&lt;/a&gt; - Mozilla 公司出品的强大的 Email 客户端程序。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.yomail.com/" target="_blank"&gt;Yomail&lt;/a&gt; - 新出的国内开发的比较好的邮件客户端。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;文件共享&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://cyberduck.io" target="_blank"&gt;Cyberduck&lt;/a&gt; - 免费 FTP，SFTP，S3 和 WebDAV 客户端 &amp;amp; OpenStack Swift Client。&lt;/li&gt; &lt;li&gt;&lt;a href="http://fivedetails.com/flow/" target="_blank"&gt;Flow&lt;/a&gt; - 支持简单的 FTP + SFTP 客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://panic.com/transmit/" target="_blank"&gt;Transmit&lt;/a&gt; - 一个 FTP 客户端，支持 FTP + SFTP + S3。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.yummysoftware.com" target="_blank"&gt;Yummy FTP&lt;/a&gt; - 专业快速，可靠的 FTP 客户端。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;数据恢复&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.alsoft.com/DiskWarrior/" target="_blank"&gt;DiskWarrior&lt;/a&gt; - 恢复文件系统损坏时，磁盘工具进行选择。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.prosofteng.com/datarescue-mac-data-recovery/" target="_blank"&gt;Data Rescue&lt;/a&gt; - 多种情况下的全面和专业的数据恢复。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.r-studio.com/data_recovery_macintosh/" target="_blank"&gt;R-Studio for Mac&lt;/a&gt; - 可恢复分区被格式化、损坏或被删除的文件。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;音频和视频&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.audacityteam.org/" target="_blank"&gt;Audacity&lt;/a&gt; - 免费开源的编辑音频的软件。&lt;a href="https://github.com/audacity/audacity" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.audacityteam.org/" target="_blank"&gt;Ardour&lt;/a&gt; - 录制，编辑和混合多轨音频。&lt;a href="https://github.com/Ardour/ardour" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.rogueamoeba.com/audiohijack/" target="_blank"&gt;Audio Hijack&lt;/a&gt; - 一个记录任何应用程序的音频，包括网络电话 Skype，网络流从 Safari，以及更多。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.arctime.org/" target="_blank"&gt;ArcTime&lt;/a&gt; - 跨平台字幕制作软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macroplant.com/adapter" target="_blank"&gt;Adapter&lt;/a&gt; - 视频，音频和图像转换工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.aegisub.org/" target="_blank"&gt;Aegisub&lt;/a&gt; - 免费、开源、跨平台的专业字幕编辑软件，可以快速打轴，制作特效字幕等，字幕组必备。 &lt;a href="https://github.com/Aegisub/Aegisub/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://cogx.org/" target="_blank"&gt;Cog&lt;/a&gt; - 一个免费的开源音频播放器。&lt;a href="https://bitbucket.org/mamburu/cog/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/AnaghSharma/Carol" target="_blank"&gt;Carol&lt;/a&gt; - 为 macOS 提供最小化和美丽的歌词应用程序。&lt;a href="https://github.com/AnaghSharma/Carol" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://mac.eltima.com/media-player.html" target="_blank"&gt;Elmedia Player&lt;/a&gt; - 支持 FLV, MP4, AVI, MOV, DAT, MKV, MP3, FLAC, M4V 等格式播放.&lt;/li&gt; &lt;li&gt;&lt;a href="http://hydrogen-music.org/" target="_blank"&gt;Hydrogen&lt;/a&gt; - 专业鼓乐类工具，创建专业但简单而直观的鼓乐节目。&lt;a href="https://github.com/hydrogen-music/hydrogen" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://handbrake.fr/" target="_blank"&gt;HandBrake&lt;/a&gt; - 高性能的视频编码和转换工具，具有很好的图形用户界面。&lt;a href="https://github.com/HandBrake/HandBrake" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.iffmpeg.com/" target="_blank"&gt;iFFmpeg&lt;/a&gt; - MacOS 上功能强大、易用的视频压制软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://lhc70000.github.io/iina/" target="_blank"&gt;IINA&lt;/a&gt; - 基于&lt;a href="https://github.com/mpv-player/mpv" target="_blank"&gt;MPV&lt;/a&gt;的，现代视频播放器，支持多点触摸控制。&lt;a href="https://github.com/lhc70000/iina" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://kodi.tv/" target="_blank"&gt;Kodi&lt;/a&gt; - 一款一流的免费开源媒体中心软件，可用于播放视频、音乐，查看图片，玩游戏等。&lt;a href="https://github.com/xbmc/xbmc" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/mifi/lossless-cut" target="_blank"&gt;LosslessCut&lt;/a&gt; - 跨平台工具，使用ffmpeg进行快速无损的视频和音频修剪。 &lt;a href="https://github.com/mifi/lossless-cut" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/ddddxxx/LyricsX" target="_blank"&gt;LyricsX&lt;/a&gt; - 一款功能完备的歌词工具。 &lt;a href="https://github.com/ddddxxx/LyricsX" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://markvapps.com/metadatics" target="_blank"&gt;Metadatics&lt;/a&gt; - 音频元数据编辑器，支持大多数常见的音频文件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://mixxx.org/" target="_blank"&gt;Mixxx&lt;/a&gt; - 免费的DJ软件，给你一切你需要的表演组合，名副其实的替代 Traktor。&lt;a href="https://github.com/mixxxdj/mixxx" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://musescore.org/" target="_blank"&gt;MuseScore&lt;/a&gt; - 免费的作曲与乐谱软件。&lt;a href="https://github.com/musescore/MuseScore" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://mpv.io/" target="_blank"&gt;MPV&lt;/a&gt; - 一个免费、开源和跨平台的媒体播放器。&lt;a href="https://github.com/mpv-player/mpv" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://evilcult.github.io/moviecatcher/" target="_blank"&gt;Movie Catcher&lt;/a&gt; - 电影美剧搜索及在线观看离线下载软件，借助百度云实现离线下载以及在线播放功能。 &lt;a href="https://github.com/EvilCult/moviecatcher" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://natron.fr/" target="_blank"&gt;Natron&lt;/a&gt; - 开源的视频合成软件，功能与 Adobe After Effects 或者 Nuke 类似。&lt;a href="https://github.com/MrKepzie/Natron" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.popcorn-time.to/mac.html" target="_blank"&gt;Popcorn Time&lt;/a&gt; - 电影播放器，观看 torrent 电影。&lt;/li&gt; &lt;li&gt;&lt;a href="https://mafintosh.github.io/playback/" target="_blank"&gt;Playback&lt;/a&gt; - 实验性质的视频播放器。&lt;a href="https://github.com/mafintosh/playback" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://perian.org/#download" target="_blank"&gt;Perian&lt;/a&gt; - 让 QuickTime 播放所有常见格式的免费插件。 &lt;a href="https://github.com/MaddTheSane/perian" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://radiant-player.github.io/radiant-player-mac/" target="_blank"&gt;Radiant Player&lt;/a&gt; - Google Play音乐播放器。&lt;a href="https://github.com/radiant-player/radiant-player-mac" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.sodaplayer.com/" target="_blank"&gt;Soda Player&lt;/a&gt; - 一款能够直接播放种子、磁力链接、在线视频、自动获取字幕、链接和本地视频文件的播放器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/sonoramac/Sonora" target="_blank"&gt;Sonora&lt;/a&gt; -  一个很小的音乐播放器。&lt;a href="https://github.com/sonoramac/Sonora" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://stringed.buenosapps.com/" target="_blank"&gt;Stringed 2&lt;/a&gt; - 音频编辑处理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.shotcut.org" target="_blank"&gt;Shotcut&lt;/a&gt; - 免费开源视频编辑器。 &lt;a href="https://github.com/mltframework/shotcut" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.telestream.net/screenflow/" target="_blank"&gt;ScreenFlow&lt;/a&gt; - 屏幕和视频编辑软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://synfig.org" target="_blank"&gt;Synfig Studio&lt;/a&gt; - 工业级、强大的 2D 矢量动画制作软件。&lt;a href="https://github.com/synfig/synfig" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/kmikiy/SpotMenu" target="_blank"&gt;SpotMenu&lt;/a&gt; - Spotify 和 iTunes 在状态菜单栏中显示。 &lt;a href="https://github.com/kmikiy/SpotMenu" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://vox.rocks/mac-music-player" target="_blank"&gt;VOX Player&lt;/a&gt; - 免费全能音乐播放器，撸码之余听听歌是一种享受。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.videolan.org/" target="_blank"&gt;VLC&lt;/a&gt; - 开源的跨平台多媒体播放器及框架，可播放大多数多媒体文件。&lt;a href="https://github.com/videolan/vlc" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://tmkk.undo.jp/xld/index_e.html" target="_blank"&gt;XLD&lt;/a&gt; - 解码/解码/转换/播放各种“无损”音频文件。&lt;a href="https://code.google.com/archive/p/xld/source" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;云音乐播放器&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/trazyn/ieaseMusic" target="_blank"&gt;ieaseMusic&lt;/a&gt; 基于网易云音乐 &lt;a href="https://github.com/Binaryify/NeteaseCloudMusicApi" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://y.qq.com/download/index.html" target="_blank"&gt;QQ音乐&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://music.163.com/" target="_blank"&gt;网易云音乐&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.xiami.com/apps/mac" target="_blank"&gt;虾米音乐&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://kuwo.cn/down/index" target="_blank"&gt;酷我音乐&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://download.kugou.com/mac.html" target="_blank"&gt;酷狗音乐&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;音频录制与编辑&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.apple.com.cn/mac/garageband/" target="_blank"&gt;GarageBand&lt;/a&gt; 来自Apple的免费数字音频工作站（DAW)，提供简介低门槛的操作界面和完整的音乐录制、剪辑制作功能&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.apple.com.cn/logic-pro/" target="_blank"&gt;Logic Pro X&lt;/a&gt; 来自Apple的专业数字音频工作站（DAW），提供完整专业的音乐制作功能、优秀的自带插件和音源，原生支持Apple Silicon实现高效运行&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;书签阅读写作&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://agenda.com/" target="_blank"&gt;Agenda&lt;/a&gt; - 以日期为重点的笔记记录应用程序，用于规划和记录您的项目。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.bear-writer.com/" target="_blank"&gt;Bear Writer&lt;/a&gt; - 漂亮，灵活的写作应用程序，用于制作笔记和散文。&lt;/li&gt; &lt;li&gt;&lt;a href="https://boostnote.io/" target="_blank"&gt;Boostnote&lt;/a&gt; - 为程序员量身定做的笔记应用。 &lt;a href="https://github.com/BoostIO/Boostnote" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://chmox.sourceforge.net/" target="_blank"&gt;Chmox&lt;/a&gt; - 读 chm 文件的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.hewbo.com/chm-reader.html" target="_blank"&gt;CHM Reader&lt;/a&gt; - 读 chm 文件的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/NSGod/ichm" target="_blank"&gt;iChm&lt;/a&gt; - 读 chm 文件的软件。&lt;a href="https://github.com/NSGod/ichm" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://joplin.cozic.net/" target="_blank"&gt;Joplin&lt;/a&gt; - 支持 markdown 的开源记事本和具有同步功能的待办事项列表管理器。 &lt;a href="https://github.com/laurent22/joplin" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.amazon.com/gp/help/customer/display.html?nodeId=201246110" target="_blank"&gt;Kindle App&lt;/a&gt; - 亚马逊 Kindle App 电子书阅读器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://klib.me/cn/" target="_blank"&gt;Klib&lt;/a&gt; - 全新的 Kindle、iBooks 标注管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://marginnote.com/?lang=zh-hans" target="_blank"&gt;MarginNote&lt;/a&gt; - 一款优秀的 PDF 有标注软件，批注、抽认卡、思维导图、汇总视图等功能。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.pdfreaderpro.com" target="_blank"&gt;PDF Reader Pro&lt;/a&gt; - 可以查看，创建，签名，转换和压缩任何 PDF 文档。&lt;/li&gt; &lt;li&gt;&lt;a href="https://pdfexpert.com/" target="_blank"&gt;PDF Expert&lt;/a&gt; - PDF 阅读、批注，编辑文本，添加照片，填写表单。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.qownnotes.org/" target="_blank"&gt;QOwnNotes&lt;/a&gt; - 是开源记事本，带有 markdown 支持和待办事项列表管理器。 &lt;a href="https://github.com/pbek/QOwnNotes" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://bananafishsoftware.com/products/spillo/" target="_blank"&gt;Spillo&lt;/a&gt; - 功能强大，美观、快速网络书签网页阅读。&lt;/li&gt; &lt;li&gt;&lt;a href="http://skim-app.sourceforge.net" target="_blank"&gt;Skim&lt;/a&gt; - PDF 阅读器和笔记本。 &lt;a href="https://sourceforge.net/projects/skim-app/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.texpad.com/" target="_blank"&gt;texpad&lt;/a&gt; - Mac 下非常棒的 LaTeX 编辑器。 支持自动编译预览，自动补全等。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.momothink.com/wonderpen" target="_blank"&gt;WonderPen&lt;/a&gt; - 专注于写作的应用，支持 markdown，很多贴心细节，支持长文写作，可导出多种格式。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Office&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.kde.org/applications/office/" target="_blank"&gt;KOffice&lt;/a&gt; - 集成化办公套件，包含文字处理器、电子 表格、幻灯片制作、项目管理等多种工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/cn/app/keynote/id409183694?mt=12" target="_blank"&gt;Keynote 讲演&lt;/a&gt; 构建炫目的演示文稿。&lt;/li&gt; &lt;li&gt;&lt;a href="https://zh-cn.libreoffice.org/" target="_blank"&gt;LibreOffice&lt;/a&gt; - 一款功能强大的免费开源办公软件，默认使用开放文档格式，并支持其他多种文档格式。&lt;a href="http://zh-cn.libreoffice.org/download/source-code/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://products.office.com/zh-cn/mac" target="_blank"&gt;Microsoft Office&lt;/a&gt; 微软Office办公套件&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/cn/app/numbers/id409203825?mt=12" target="_blank"&gt;Numbers 表格&lt;/a&gt; 创建令人印象深刻的电子表格。&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/cn/app/pages/id409201541?mt=12" target="_blank"&gt;Pages 文稿&lt;/a&gt; 引人注目的文稿。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.wps.cn/product/wpsmac/" target="_blank"&gt;WPS&lt;/a&gt; - 是一套跨平台的办公室软件套件。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;RSS&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.rockysandstudio.com/" target="_blank"&gt;Leaf&lt;/a&gt; - RSS 客户端程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://ranchero.com/netnewswire/" target="_blank"&gt;NetNewsWire&lt;/a&gt; - 免费的 RSS 阅读器。&lt;a href="https://github.com/brentsimmons/NetNewsWire" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://readkitapp.com/" target="_blank"&gt;ReadKit&lt;/a&gt; - 书签 RSS 管理客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="http://reederapp.com/" target="_blank"&gt;Reeder 4&lt;/a&gt; - RSS 服务订阅。&lt;/li&gt; &lt;li&gt;&lt;a href="http://viennarss.github.io/" target="_blank"&gt;Vienna&lt;/a&gt; - RSS/Atom 新闻阅读客户端。&lt;a href="https://github.com/ViennaRSS/vienna-rss" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://irreader.fatecore.com" target="_blank"&gt;irreader&lt;/a&gt; - 多功能的 RSS 阅读器，支持订阅播客和任何网站。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Markdown&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;A curated list of delightful Markdown stuff. &lt;a href="https://github.com/BubuAnabelas/awesome-markdown#tools" target="_blank"&gt;&lt;img src="https://camo.githubusercontent.com/491b767d4b8fe216d87d95e28b52a53d86683974780fb6110b0cc225edf6e2d8/68747470733a2f2f6a617977636a6c6f76652e6769746875622e696f2f73622f69636f2f6d696e2d617765736f6d652e737667" alt="Awesome List" title="Awesome List" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://www.zybuluo.com/" target="_blank"&gt;Cmd Markdown&lt;/a&gt; - Cmd Markdown 编辑阅读器，支持实时同步预览，区分写作和阅读模式，支持在线存储，分享文稿网址。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/egoist/eme" target="_blank"&gt;EME&lt;/a&gt; - 最近刚出的一款 Markdown 编辑器，界面很像 Chrome 浏览器的界面，很简约。&lt;/li&gt; &lt;li&gt;&lt;a href="https://ia.net/writer/" target="_blank"&gt;iA Writer&lt;/a&gt; - Markdown 文本预览编辑，注重语法检查，专门为作家提供的编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://lightpaper.42squares.in/" target="_blank"&gt;LightPaper&lt;/a&gt; - 简单的 Markdown 文本编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://marp.app" target="_blank"&gt;Marp&lt;/a&gt; -  Markdown 制作幻灯片编辑器。&lt;a href="https://github.com/yhatt/marp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://zh.mweb.im/" target="_blank"&gt;MWeb&lt;/a&gt; - 专业的 Markdown 写作、记笔记、静态博客生成软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://macdown.uranusjr.com/" target="_blank"&gt;MacDown&lt;/a&gt; - 一款开源的 Markdown 编辑器，深受&lt;a href="http://25.io/mou/" target="_blank"&gt;Mou&lt;/a&gt;的影响。** 开源 **&lt;/li&gt; &lt;li&gt;&lt;a href="http://marked2app.com/" target="_blank"&gt;Marked 2&lt;/a&gt; - Markdown 文本预览编辑，为所有作家提供一套优雅而强大的工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.textnutwriter.com/" target="_blank"&gt;TextNut&lt;/a&gt; - Markdown 编辑器，富文本之间自由切换。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.typora.io/" target="_blank"&gt;Typora&lt;/a&gt; - 基于 Electron 的“读写一体” Markdown 编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.ulyssesapp.com/features/" target="_blank"&gt;Ulysses&lt;/a&gt; - 适用于 Mac，iPad 和 iPhone 的写作应用程序，支持 Markdown。&lt;/li&gt; &lt;li&gt;&lt;a href="https://ivarptr.github.io/yu-writer.site/" target="_blank"&gt;Yu Writer&lt;/a&gt; - 一款能找到写作乐趣的 Markdown 文本编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://maxiang.io/" target="_blank"&gt;马克飞象&lt;/a&gt; - Markdown 编辑器，提供 Web 端 和 App 端，多种配色，支持同步到 印象笔记。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.effie.co/" target="_blank"&gt;Effie&lt;/a&gt; - 轻量级 Markdown 写作软件，支持大纲笔记和思维导图。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;笔记&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://evernote.com/" target="_blank"&gt;Evernote&lt;/a&gt; - 笔记本应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://www.inkdrop.info/" target="_blank"&gt;Inkdrop&lt;/a&gt; - Markdown 爱好者的笔记本应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="http://app.leanote.com" target="_blank"&gt;leanote&lt;/a&gt; - 支持 Markdown 的一款开源笔记软件，支持直接成为个人博客。&lt;a href="https://github.com/leanote/leanote" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="http://www.get-notes.com/" target="_blank"&gt;Notes&lt;/a&gt; - 简洁的笔记应用。 &lt;a href="https://github.com/nuttyartist/notes" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://www.zoho.com/notebook/notebook-for-mac.html" target="_blank"&gt;Notebook&lt;/a&gt; 漂亮的笔记本应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://www.notion.so/zh-cn" target="_blank"&gt;Notion&lt;/a&gt; - 一个集大成的富文本笔记管理软件，支持丰富却又简单明了的的文字格式，甚至覆盖了&lt;code&gt;TODO&lt;/code&gt;类软件的功能。数据在服务端存储，支持web访问，也提供了&lt;code&gt;macOS&lt;/code&gt;/&lt;code&gt;Windows&lt;/code&gt;/&lt;code&gt;iOS&lt;/code&gt;/&lt;code&gt;安卓&lt;/code&gt;等平台客户端。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://www.onenote.com/" target="_blank"&gt;OneNote&lt;/a&gt; - 微软备注应用。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="http://happenapps.com/#quiver" target="_blank"&gt;Quiver&lt;/a&gt; - 程序猿的笔记本。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="http://note.youdao.com/" target="_blank"&gt;有道云笔记&lt;/a&gt; - 支持多目录，Markdown，iWork/Office 预览。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="http://www.wiz.cn/download.html" target="_blank"&gt;为知笔记&lt;/a&gt; - 支持 Markdown，搜集整理图片链接导入文档。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;a href="https://www.yuque.com/install/desktop" target="_blank"&gt;阿里语雀&lt;/a&gt; - 云笔记类知识管理、协作平台，基于Markdown写作，支持内嵌流程图、脑图、时序、代码渲染以及Sketch画板创作，个人知识分享等！相比有道云笔记、印象笔记同类产品，包含其全部的功能以外，支持知识分享以及更强大的创作、协作、编辑器，它来自阿里巴巴蚂蚁金服。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;制作电子书&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://calibre-ebook.com/" target="_blank"&gt;Calibre&lt;/a&gt; - 丑陋的软件，但强大的软件电子书管理和转换。&lt;a href="https://github.com/kovidgoyal/calibre" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://sigil-ebook.com/" target="_blank"&gt;Sigil&lt;/a&gt; - 多平台 EPUB 编辑器。 &lt;a href="https://github.com/Sigil-Ebook/Sigil" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.scribus.net/" target="_blank"&gt;Scribus&lt;/a&gt; - 开源电子杂志制作软件。 &lt;a href="https://sourceforge.net/projects/scribus/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;软件打包工具&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://appjs.com/" target="_blank"&gt;AppJS&lt;/a&gt; - 使用 JS、HTML 和CSS 构建跨平台的桌面应用程序。&lt;a href="https://github.com/appjs/appjs" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/AlloyTeam/webtop" target="_blank"&gt;AlloyDesktop&lt;/a&gt; - 同上，腾讯出品，给个差评。&lt;a href="https://github.com/AlloyTeam/webtop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/sindresorhus/create-dmg" target="_blank"&gt;create-dmg&lt;/a&gt; - 快速创建一个压缩镜像文件。 &lt;a href="https://github.com/sindresorhus/create-dmg" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://electronjs.org/" target="_blank"&gt;Electron&lt;/a&gt; - 前身是 AtomShell，使用 JS、HTML 和 CSS 构建跨平台的桌面应用程序。&lt;a href="https://github.com/electron/electron" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/pojala/electrino" target="_blank"&gt;Electrino&lt;/a&gt; - 使用 JS、HTML 和 CSS 构建跨平台的桌面应用程序，构建出的应用体积比 Electron 小。&lt;a href="https://github.com/pojala/electrino" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://johnste.github.io/finicky/" target="_blank"&gt;Finicky&lt;/a&gt; - Web 应用程序转化为苹果的应用程序。&lt;a href="https://github.com/johnste/finicky" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://hex.youdao.com/zh-cn/index.html" target="_blank"&gt;HEX&lt;/a&gt; - 使用 JS、HTML 和 CSS 构建跨平台的桌面应用程序，有道出品。&lt;a href="https://github.com/netease-youdao/hex" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://ionicframework.com/" target="_blank"&gt;ionic&lt;/a&gt; - 一个用来开发混合手机应用的，开源的，免费的代码库。 &lt;a href="https://github.com/driftyco/ionic" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://nwjs.io" target="_blank"&gt;nw.js&lt;/a&gt; - 使用 HTML 和 JavaScript 来制作桌面应用。&lt;a href="https://github.com/nwjs/nw.js" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://macgapproject.github.io/" target="_blank"&gt;MacGap&lt;/a&gt; - 桌面 WebKit 打包 HTML、CSS、JS 应用。&lt;a href="https://github.com/MacGapProject" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://reactdesktop.js.org" target="_blank"&gt;react-desktop&lt;/a&gt; - 为 macOS Sierra带来 React UI 组件。&lt;a href="https://github.com/gabrielbull/react-desktop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://microsoft.github.io/reactxp/" target="_blank"&gt;ReactXP&lt;/a&gt; - 微软官方出品，支持平台 Web，iOS，Android 和 Windows UWP 仍然是一项正在进行的工作。&lt;a href="https://github.com/microsoft/reactxp" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/ptmt/react-native-desktop" target="_blank"&gt;React Native macOS&lt;/a&gt; - 用 React Native 技术构建 OS X 下的桌面应用程序。&lt;a href="https://github.com/ptmt/react-native-desktop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/CanonicalLtd/react-native" target="_blank"&gt;React Native Desktop for Ubuntu&lt;/a&gt; - 用 React Native 技术构建 Ubuntu 下的桌面应用程序。&lt;a href="https://github.com/CanonicalLtd/react-native" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;下载工具&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://aria2.github.io/" target="_blank"&gt;aria2&lt;/a&gt; - 一款支持多种协议的轻量级命令行下载工具。&lt;a href="https://github.com/aria2" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://software.charliemonroe.net/downie.php" target="_blank"&gt;Downie&lt;/a&gt; - 支持多达近 1200 个视频站点的视频下载工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.freedownloadmanager.org/" target="_blank"&gt;Free Download Manager&lt;/a&gt; - 功能强大的下载加速器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://mac.eltima.com/download-manager.html" target="_blank"&gt;FOLX&lt;/a&gt; - 一个 Mac osx 系统风格界面的下载管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://jdownloader.org/" target="_blank"&gt;JDownloader&lt;/a&gt; - 下载工具，下载文件的一键式托管。&lt;/li&gt; &lt;li&gt;&lt;a href="https://motrix.app/" target="_blank"&gt;Motrix&lt;/a&gt; - Motrix 是一款全能的下载工具，支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源。 &lt;a href="https://github.com/agalwood/Motrix" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.qbittorrent.org/" target="_blank"&gt;qBittorrent&lt;/a&gt; - 一个替代 μTorrent 的开源软件。 &lt;a href="https://github.com/qbittorrent/qBittorrent" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.transmissionbt.com/" target="_blank"&gt;Transmission&lt;/a&gt; - 免费的 BitTorrent 客户端 &lt;a href="https://github.com/transmission/transmission" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://you-get.org/" target="_blank"&gt;You-Get&lt;/a&gt; - 网络富媒体命令行下载工具。&lt;a href="https://github.com/soimort/you-get" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;网盘&lt;/h2&gt; &lt;p&gt;&lt;em&gt;推荐一些有Mac客户端的网盘。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://pc.115.com/" target="_blank"&gt;115&lt;/a&gt; - 115 云客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.dropboxchina.com/Download/dropbox-for-mac.html" target="_blank"&gt;Dropbox&lt;/a&gt; - 非常好用的免费网络文件同步工具，提供在线存储服务。&lt;/li&gt; &lt;li&gt;&lt;a href="https://nextcloud.com/" target="_blank"&gt;NextCloud&lt;/a&gt; - 基于 ownCloud 完全开源免费开源，企业文件同步和共享。&lt;/li&gt; &lt;li&gt;&lt;a href="https://mega.nz" target="_blank"&gt;Mega&lt;/a&gt; - 免费的云服务，提供 50GB 的免费存储空间。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.resilio.com/individuals/" target="_blank"&gt;Resilio Sync&lt;/a&gt; - P2P私有云盘，BitTorrent血统，支持&lt;code&gt;安卓&lt;/code&gt;/&lt;code&gt;iOS&lt;/code&gt;/&lt;code&gt;Windows&lt;/code&gt;/&lt;code&gt;macOS&lt;/code&gt;/&lt;code&gt;Linux&lt;/code&gt;/&lt;code&gt;FreeBSD&lt;/code&gt;/&lt;code&gt;NAS&lt;/code&gt;等系统平台。注意：截止2021.7.20，macOS平台客户端存在休眠崩溃现象，除此之外可以正常使用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.seafile.com/" target="_blank"&gt;Seafile&lt;/a&gt; - 是由国内团队开发的国际化的开源云存储软件项目。&lt;/li&gt; &lt;li&gt;&lt;a href="https://syncthing.net/" target="_blank"&gt;Syncthing&lt;/a&gt; - &lt;a href="https://www.resilio.com/individuals/" target="_blank"&gt;Resilio Sync&lt;/a&gt;的开源竞争者，架构上更加开放自由，良好的用户文档，基于Go语言支持大量系统平台，甚至包括&lt;code&gt;OpenWrt&lt;/code&gt;！此项目的界面翻译工作也支持&lt;a href="https://www.transifex.com/syncthing/syncthing/dashboard/" target="_blank"&gt;开源共建&lt;/a&gt;。&lt;a href="https://github.com/syncthing/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://owncloud.org" target="_blank"&gt;ownCloud&lt;/a&gt; - 私有云网盘。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pan.baidu.com/download?from=header#pan" target="_blank"&gt;百度云&lt;/a&gt; - 百度云客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.weiyun.com/" target="_blank"&gt;腾讯微云&lt;/a&gt; - 腾讯云客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.jianguoyun.com/s/downloads" target="_blank"&gt;坚果云&lt;/a&gt; - 坚果云客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.fangcloud.com/" target="_blank"&gt;亿方云&lt;/a&gt; - 硅谷团队打造，个人免费。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.aliyundrive.com/drive/" target="_blank"&gt;阿里云盘&lt;/a&gt; - 阿里云盘。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;输入法&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/utatti/kawa" target="_blank"&gt;Kawa&lt;/a&gt; - 给每个输入法定义一个快捷键. &lt;a href="https://github.com/utatti/kawa" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://rime.im/" target="_blank"&gt;RIME&lt;/a&gt; - 中州韻輸入法引擎。&lt;a href="https://github.com/rime" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://matthewpalmer.net/rocket/" target="_blank"&gt;Rocket&lt;/a&gt; - 使用冒号快捷键可以更快捷地输入表情符号。&lt;/li&gt; &lt;li&gt;&lt;a href="http://matthewpalmer.net/rocket/" target="_blank"&gt;Type2Phone&lt;/a&gt; - 把 Macbook 键盘变为 iPhone 的蓝牙键盘。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.glamtime.com.cn/product/wbim" target="_blank"&gt;WBIM&lt;/a&gt; - 五笔输入法。&lt;/li&gt; &lt;li&gt;~~&lt;a href="http://qq.pinyin.cn/" target="_blank"&gt;QQ输入法&lt;/a&gt; - 腾讯出品的输入法。~~&lt;/li&gt; &lt;li&gt;&lt;a href="http://pinyin.sogou.com/mac/" target="_blank"&gt;搜狗输入法&lt;/a&gt; - 搜狗输入法。&lt;/li&gt; &lt;li&gt;&lt;a href="https://srf.baidu.com/input/mac.html" target="_blank"&gt;百度输入法&lt;/a&gt; - 支持拼音五笔输入。&lt;/li&gt; &lt;li&gt;&lt;a href="https://im.logcg.com/loginputmac" target="_blank"&gt;落格输入法&lt;/a&gt; - 打破 Mac 双拼多年来的窘境。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omicronlab.com/iavro.html" target="_blank"&gt;iAvro&lt;/a&gt; - 孟加拉语输入法。 &lt;a href="https://github.com/torifat/iAvro" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://qingg.im/index.html" target="_blank"&gt;清歌五笔输入法&lt;/a&gt; - 为 iOS 和 Mac 专门打造的五笔输入法。&lt;/li&gt; &lt;li&gt;&lt;a href="https://itunes.apple.com/cn/app/yan-wen-zi/id914708191?mt=12" target="_blank"&gt;颜文字&lt;/a&gt; - 颜文字输入。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/dongyuwei/hallelujahIM" target="_blank"&gt;哈利路亚英文输入法&lt;/a&gt; - 智能英文输入法，具备自动补全，自动纠错功能。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;浏览器&lt;/h2&gt; &lt;p&gt;&lt;em&gt;这里放Mac的浏览器应用&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://brave.com/" target="_blank"&gt;Brave&lt;/a&gt; - 用 Brave 浏览更快更安全。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.google.cn/chrome/browser/" target="_blank"&gt;Chrome&lt;/a&gt; - Chrome 浏览器谷歌出品。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.firefox.com.cn/" target="_blank"&gt;Firefox&lt;/a&gt; - 迎接 Firefox Quantum。快，只为更好。火狐浏览器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.apple.com/cn/safari/" target="_blank"&gt;Safari&lt;/a&gt; - Mac 预装自带浏览器。&lt;a href="https://github.com/learn-anything/safari-extensions#readme" target="_blank"&gt;&lt;img src="https://camo.githubusercontent.com/491b767d4b8fe216d87d95e28b52a53d86683974780fb6110b0cc225edf6e2d8/68747470733a2f2f6a617977636a6c6f76652e6769746875622e696f2f73622f69636f2f6d696e2d617765736f6d652e737667" alt="Awesome List" title="Awesome List" /&gt;&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.opera.com/zh-cn" target="_blank"&gt;Opera&lt;/a&gt; - Opera 浏览器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://browser.qq.com/mac/" target="_blank"&gt;QQ浏览器&lt;/a&gt; - QQ 浏览器－腾讯出品。&lt;/li&gt; &lt;li&gt;&lt;a href="https://vivaldi.com/" target="_blank"&gt;Vivaldi&lt;/a&gt; - Opera 开发商出品新的浏览器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://oryoki.io/" target="_blank"&gt;Ōryōki&lt;/a&gt; - 小的 web 浏览器。这是一个试验性的项目，目前正在开发中&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.maxthon.cn/mac/" target="_blank"&gt;傲游云浏览器&lt;/a&gt; - 傲游云浏览器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://browser.360.cn/ee/mac/index.html" target="_blank"&gt;360极速浏览器&lt;/a&gt; - 更好用，不将就。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;翻译工具&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://cidian.youdao.com/multi.html" target="_blank"&gt;有道翻译&lt;/a&gt; - 有道词典桌面版。&lt;/li&gt; &lt;li&gt;&lt;a href="http://cidian.dict.cn/mac.html" target="_blank"&gt;辞海词典&lt;/a&gt; - 学单词、背单词、辞海词典。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.eudic.net/eudic/mac_dictionary.aspx" target="_blank"&gt;eudic&lt;/a&gt; - 欧路词典词典。&lt;/li&gt; &lt;li&gt;&lt;a href="https://app.grammarly.com/" target="_blank"&gt;Grammarly&lt;/a&gt; - 修正英语语法及用语&lt;/li&gt; &lt;li&gt;&lt;a href="https://toolinbox.net/iText/" target="_blank"&gt;iText&lt;/a&gt; - 截图识别文字、翻译&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.itranslate.com/" target="_blank"&gt;iTranslate&lt;/a&gt; - 支持全世界超过 80 种语言发音和输出。&lt;/li&gt; &lt;li&gt;&lt;a href="https://ludwig.guru" target="_blank"&gt;Ludwig&lt;/a&gt; - 语言搜索引擎，可帮助您用英语写得更好。&lt;/li&gt; &lt;li&gt;&lt;a href="http://translate-tab.com/" target="_blank"&gt;Translate Tab&lt;/a&gt; - 菜单栏翻译插件，封装了谷歌翻译，支持自动识别语言。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/ripperhe/Bob" target="_blank"&gt;Bob&lt;/a&gt; - 简小好用的翻译工具，支持语言自动检测，截图翻译。&lt;a href="https://github.com/ripperhe/Bob" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://translatium.app" target="_blank"&gt;Translatium&lt;/a&gt; - 在 100 多种语言之间翻译单词、短语和图像，并提供字典、音译和语音输出支持。 &lt;a href="https://github.com/webcatalog/translatium-desktop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;安全工具&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://cleanerone.trendmicro.com/antivirus-one-for-mac/?utm_source=github&amp;amp;utm_medium=referral&amp;amp;utm_campaign=githubproject" target="_blank"&gt;Antivirus One&lt;/a&gt; - 值得信赖的Mac安全保护工具：保护您的 Mac 免受病毒、恶意软件和广告软件的侵害。阻止潜在的 Web威胁并保护您的Mac免受漏洞影响。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/blockblock.html" target="_blank"&gt;BlockBlock&lt;/a&gt; - 恶意软件会自行安装，以确保它在重新引导时自动重新执行。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/dhs.html" target="_blank"&gt;Dylib Hijack Scanner&lt;/a&gt; - Dylib 劫机扫描仪或 DHS，是一个简单的实用程序，将扫描您的计算机的应用程序是易受 dylib 劫持或被劫持。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macpaw.com/encrypto" target="_blank"&gt;Encrypto&lt;/a&gt; - 免费加密工具，用于加密文件和文件夹&lt;/li&gt; &lt;li&gt;&lt;a href="https://gpgtools.org/" target="_blank"&gt;GPG Suite&lt;/a&gt; - macOS平台的一站式GnuPG解决方案，提供命令行和GUI的加解密工具。开箱即用的&lt;code&gt;gpg-agent&lt;/code&gt;密码缓存服务，还包括一个GUI的pinenry程序，支持与macOS原生钥匙串集成。&lt;a href="https://gpgtools.org/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/kextviewr.html" target="_blank"&gt;KextViewer&lt;/a&gt; - 查看所有在 OS 内核中加载的模块。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/knockknock.html" target="_blank"&gt;KnockKnock&lt;/a&gt; - “谁在那？” 查看Mac上持久安装的内容。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/halo/LinkLiar" target="_blank"&gt;LinkLiar&lt;/a&gt; - 可以帮助你哄骗 Wi-Fi 和以太网接口的 MAC 地址。 &lt;a href="https://github.com/halo/LinkLiar" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/lulu.html" target="_blank"&gt;LuLu&lt;/a&gt; - 免费的macOS防火墙，旨在阻止未经授权（传出）的网络流量。 &lt;a href="https://www.tinc-vpn.org/git/browse?p=tinc" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.murusfirewall.com/" target="_blank"&gt;Murus&lt;/a&gt; - 强大、灵活且易于理解使用的防火墙，官方提供多种不同的APP以提供不同功能的组合。最基础的免费版本&lt;code&gt;Murus Lite&lt;/code&gt;是纯粹基于传入端口的防火墙，跟基于应用程序的macOS原生防火墙形成有效互补。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/oversight.html" target="_blank"&gt;OverSight&lt;/a&gt; - 监控 Mac 的麦克风和网络摄像头，当内部麦克风被激活，或者当进程访问摄像头时提醒用户。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/ransomwhere.html" target="_blank"&gt;RansomWhere?&lt;/a&gt; - 通用 Ransomware 检测。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/taskexplorer.html" target="_blank"&gt;TaskExplorer&lt;/a&gt; - 使用 TaskExplorer 探索在 Mac 上运行的所有任务（进程）。&lt;/li&gt; &lt;li&gt;&lt;a href="https://objective-see.com/products/whatsyoursign.html" target="_blank"&gt;What's Your Sign?&lt;/a&gt; - 验证文件的加密签名可以推断其来源或可信度。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;科学上网&lt;/h2&gt; &lt;p&gt;&lt;em&gt;假设你是个勤奋的同学，你总有一天会强烈需要它们，上帝保佑他们吧。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/trailofbits/algo" target="_blank"&gt;Algo&lt;/a&gt; - 在云中设置个人 IPSEC VPN。 &lt;a href="https://github.com/trailofbits/algo" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/yichengchen/clashX" target="_blank"&gt;ClashX&lt;/a&gt; - 基于 clash 的一款支持规则过滤的科学上网工具。 &lt;a href="https://github.com/yichengchen/clashX" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://getlantern.org" target="_blank"&gt;Lantern&lt;/a&gt; - 科学上网。&lt;a href="https://github.com/getlantern/lantern" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://shadowsocks.org/" target="_blank"&gt;ShadowsocksX&lt;/a&gt; - 一个快速的隧道代理，可以帮助你绕过防火墙。&lt;a href="https://github.com/shadowsocks" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/qiuyuzhou/ShadowsocksX-NG" target="_blank"&gt;ShadowsocksX-NG&lt;/a&gt; - 一款 ShadowsocksX 客户端软件。&lt;a href="https://github.com/qiuyuzhou/ShadowsocksX-NG" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://nssurge.com/" target="_blank"&gt;Surge&lt;/a&gt; - 科学上网。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.shimovpn.com/" target="_blank"&gt;Shimo&lt;/a&gt; - 连接大量 VPN 的应用&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.tunnelbear.com" target="_blank"&gt;Tunnelbear&lt;/a&gt; - 简单的私人 VPN。&lt;/li&gt; &lt;li&gt;&lt;a href="https://tunnelblick.net/downloads.html" target="_blank"&gt;Tunnelblick&lt;/a&gt; - OpenVPN 的免费软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.tinc-vpn.org" target="_blank"&gt;tinc&lt;/a&gt; - VPN 软件. &lt;a href="https://www.tinc-vpn.org/git/browse?p=tinc" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.v2ray.com/" target="_blank"&gt;V2Ray&lt;/a&gt; - 原生支持 Socks、HTTP、Shadowsocks、VMess 等协议。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;其它实用工具&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/fancymax/12306ForMac" target="_blank"&gt;12306ForMac&lt;/a&gt; - Mac 版 12306 订票/检票助手。&lt;a href="https://github.com/fancymax/12306ForMac" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://1440app.com/" target="_blank"&gt;1440 Minutes Left Today&lt;/a&gt; - 在菜单栏中，直接记录到一天结束还剩多少分钟。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.airserver.com/Download" target="_blank"&gt;AirServer&lt;/a&gt; - 将手机投影到电脑上。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.alfredapp.com/" target="_blank"&gt;Alfred&lt;/a&gt; - 效率神器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.raycast.com/" target="_blank"&gt;Raycast&lt;/a&gt; - 类似Alfred功能，重要的是免费。&lt;/li&gt; &lt;li&gt;&lt;a href="https://getbitbar.com/" target="_blank"&gt;BitBar&lt;/a&gt; - 支持使用各种语言将信息展示到Mac OS的菜单栏。&lt;a href="https://github.com/matryer/bitbar" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://macitbetter.com/" target="_blank"&gt;BetterZip&lt;/a&gt; - 压缩解压缩工具支持格式 ZIP、TAR、TGZ、TBZ、TXZ (new)、7-ZIP、RAR。&lt;/li&gt; &lt;li&gt;&lt;a href="https://folivora.ai/" target="_blank"&gt;BetterTouchTool&lt;/a&gt; - 代替默认的系统操作方式（组合键、修饰键、手势等）。&lt;/li&gt; &lt;li&gt;&lt;a href="https://hluk.github.io/CopyQ" target="_blank"&gt;CopyQ&lt;/a&gt; - 高级功能剪贴板管理工具。 &lt;a href="https://github.com/hluk/CopyQ" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.controlplaneapp.com/" target="_blank"&gt;ControlPlane&lt;/a&gt; - 自定义 Mac 情景模式。某些场景让 Mac 自动静音或是自动打开 Mail 客户端等等。&lt;a href="https://github.com/dustinrue/ControlPlane" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.clipmenu.com" target="_blank"&gt;ClipMenu&lt;/a&gt; - 一个剪贴板操作的管理器。&lt;a href="https://github.com/naotaka/ClipMenu" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://clipy-app.com/" target="_blank"&gt;Clipy&lt;/a&gt; - 基于 ClipMenu 继续开发的强大的剪切板管理器。 &lt;a href="https://github.com/Clipy/Clipy" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mediaatelier.com/CheatSheet/" target="_blank"&gt;CheatSheet&lt;/a&gt; - CheatSheet 是一款 Mac 上的非常实用的快捷键快速提醒工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://daisydiskapp.com/" target="_blank"&gt;DaisyDisk&lt;/a&gt; - 磁盘空间使用扫描工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/greenboxal/dns-heaven" target="_blank"&gt;DNS Heaven&lt;/a&gt; - 可以令基于 glibc 的 macOS 应用直接使用原生栈来解析 DNS，主要适用于 VPN。 &lt;a href="https://github.com/greenboxal/dns-heaven" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://ezip.awehunt.com" target="_blank"&gt;eZip&lt;/a&gt; - 界面简洁，功能完善，支持主流的多种压缩格式。支持 Mojave 深色模式、QuickLook预览、拖拽解压。&lt;/li&gt; &lt;li&gt;&lt;a href="https://justgetflux.com/" target="_blank"&gt;f.lux&lt;/a&gt; - 自动调整您的电脑屏幕，以匹配亮度。&lt;/li&gt; &lt;li&gt;&lt;a href="https://lunar.fyi/" target="_blank"&gt;Lunar&lt;/a&gt; - 外接显示器亮度/对比度调节工具，从此告别物理按键。&lt;a href="https://github.com/alin23/Lunar" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.hammerspoon.org/" target="_blank"&gt;Hammerspoon&lt;/a&gt; - 功能强大的自动化工具，Lua 脚本驱动，支持窗口管理。&lt;a href="https://github.com/Hammerspoon/hammerspoon" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.httrack.com" target="_blank"&gt;HTTrack&lt;/a&gt; - 可以下载整个网站和离线浏览。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/niw/HapticKey/releases" target="_blank"&gt;HapticKey&lt;/a&gt; - Touch Bar 触觉反馈。 &lt;a href="https://github.com/niw/HapticKey" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://sourceforge.net/projects/hwsensors" target="_blank"&gt;HWSensors&lt;/a&gt; - 自带FakeSMC的黑苹果硬件状态监控插件。 &lt;a href="https://bitbucket.org/kozlek/hwsensors/overview" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://zhengying.github.io/hungrymark" target="_blank"&gt;Hungrymark&lt;/a&gt; - 非常有用的收藏夹应用，收藏文件，文件夹，网址，快速的通过状态栏菜单访问这些书签。&lt;/li&gt; &lt;li&gt;&lt;a href="https://bjango.com/mac/istatmenus/" target="_blank"&gt;iStat pro&lt;/a&gt; - 免费的 Mac OS 电脑硬件信息检测软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mowglii.com/itsycal/" target="_blank"&gt;Itsycal&lt;/a&gt; - 一款简洁实用的开源日历工具。&lt;a href="https://github.com/sfsam/itsycal" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://pqrs.org/osx/karabiner/" target="_blank"&gt;Karabiner&lt;/a&gt; - 一个强大的和稳定的 OS X 的键盘定制。&lt;a href="https://github.com/tekezo/Karabiner" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.keyboardmaestro.com" target="_blank"&gt;Keyboard Maestro&lt;/a&gt; - 根据键盘，菜单，位置，添加的设备等触发器自动执行日常操作。&lt;/li&gt; &lt;li&gt;&lt;a href="http://keytty.com" target="_blank"&gt;Keytty&lt;/a&gt; - 让你通过键盘使用鼠标。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.keka.io/zh-cn/" target="_blank"&gt;Keka&lt;/a&gt; - 一个免费的 macOS 文件解压缩程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://sindresorhus.com/lungo" target="_blank"&gt;Lungo&lt;/a&gt; - 防止Mac进入睡眠状态。&lt;/li&gt; &lt;li&gt;&lt;a href="http://memo-app.net/" target="_blank"&gt;Memo&lt;/a&gt; - 给你的便笺加个密。&lt;/li&gt; &lt;li&gt;&lt;a href="https://manta.life/" target="_blank"&gt;Manta&lt;/a&gt; - 灵活的发票桌面应用程序，漂亮和可定制模板。&lt;a href="https://github.com/hql287/Manta" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://mos.caldis.me/" target="_blank"&gt;Mos&lt;/a&gt; - 让你的鼠标滚轮丝滑如触控板。&lt;a href="https://github.com/Caldis/Mos" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/kaunteya/MacCacheCleaner" target="_blank"&gt;Mac Cache Cleaner&lt;/a&gt; - 缓存清理工具 &lt;a href="https://github.com/kaunteya/MacCacheCleaner" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://numi.io/" target="_blank"&gt;Numi&lt;/a&gt; - 漂亮的计算器应用。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.macupdate.com/app/mac/37991/nosleep" target="_blank"&gt;NoSleep&lt;/a&gt; - 合上盖子不休眠，可根据是否连接电源单独设置。&lt;/li&gt; &lt;li&gt;&lt;a href="http://openemu.org/" target="_blank"&gt;openEmu&lt;/a&gt; - 模拟器，可以玩魂斗罗之类，轻松回到小时候。&lt;a href="https://github.com/OpenEmu/OpenEmu" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/more" target="_blank"&gt;OmniDiskSweeper&lt;/a&gt; - 磁盘空间使用扫描工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/omniplan/" target="_blank"&gt;OmniPlan&lt;/a&gt; - 项目管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pasteapp.me" target="_blank"&gt;Paste&lt;/a&gt; - 智能剪贴板历史片段管理。&lt;/li&gt; &lt;li&gt;&lt;a href="https://tapbots.com/pastebot/" target="_blank"&gt;PasteBot&lt;/a&gt; - 强大的剪贴板管理器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/JulianKahnert/PDF-Archiver" target="_blank"&gt;PDF Archiver&lt;/a&gt; - 一个用于标记和归档任务的好工具。&lt;a href="https://github.com/JulianKahnert/PDF-Archiver" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://qotoqot.com/qbserve/" target="_blank"&gt;Qbserve&lt;/a&gt; - 观察你如何度过你的时间。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.rescuetime.com/" target="_blank"&gt;RescueTime&lt;/a&gt; - 个人分析服务，向您展示如何花时间和提供工具来帮助您提高工作效率。&lt;/li&gt; &lt;li&gt;&lt;a href="http://indragie.com/snap" target="_blank"&gt;Snap&lt;/a&gt; - 一款可以给 Dock 上的程序添加快捷键的小工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/jamieweavis/streaker" target="_blank"&gt;Streaker&lt;/a&gt; - GitHub贡献和统计跟踪菜单栏应用程序。 &lt;a href="https://github.com/jamieweavis/streaker" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://theunarchiver.com/" target="_blank"&gt;The Unarchiver&lt;/a&gt; - 解压许多不同种类的归档压缩文件。 &lt;a href="https://bitbucket.org/kosovan/theunarchiver" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://timingapp.com/" target="_blank"&gt;Timing&lt;/a&gt; - Mac 的自动时间和生产力跟踪。&lt;/li&gt; &lt;li&gt;&lt;a href="https://cleanerone.trendmicro.com/unarchiver-one/?utm_source=github&amp;amp;utm_medium=referral&amp;amp;utm_campaign=githubproject" target="_blank"&gt;Unarchive One&lt;/a&gt; - 快速解压单个多个不同种类的压缩文件/压缩文件到各类常见压缩格式。&lt;/li&gt; &lt;li&gt;&lt;a href="http://scripts.sil.org/ukelele" target="_blank"&gt;Ukelele&lt;/a&gt; - Unicode 键盘布局编辑器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/insidegui/WWDC" target="_blank"&gt;WWDC&lt;/a&gt; - Mac OS 的非官方的 WWDC APP。&lt;a href="https://github.com/insidegui/WWDC" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://xscopeapp.com/" target="_blank"&gt;xScope&lt;/a&gt; - 测量、检查和测试屏幕上的图形和布局的工具。搜索你的苹果和网络，快速打开应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://yasuo.360.cn/mac/index.html" target="_blank"&gt;360压缩&lt;/a&gt; - 简单易用，免费无广告的压缩工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/cn/app/%E8%B6%85%E7%BA%A7%E5%8F%B3%E9%94%AE-irightmouse/id1497428978?mt=12" target="_blank"&gt;超级右键&lt;/a&gt; - 一款finder右键菜单扩展，包括了大量便捷工具比如新建文件，直接打开终端等&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;剪贴板工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.clipmenu.com" target="_blank"&gt;ClipMenu&lt;/a&gt; - 一个剪贴板操作的管理器。&lt;a href="https://github.com/naotaka/ClipMenu" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://hluk.github.io/CopyQ" target="_blank"&gt;CopyQ&lt;/a&gt; - 高级功能剪贴板管理工具。 &lt;a href="https://github.com/hluk/CopyQ" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://en.toolinbox.net/iPaste" target="_blank"&gt;iPaste&lt;/a&gt; - 轻巧高效的剪贴板工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://pasteapp.me" target="_blank"&gt;Paste&lt;/a&gt; - 智能剪贴板历史片段管理。&lt;/li&gt; &lt;li&gt;&lt;a href="https://tapbots.com/pastebot/" target="_blank"&gt;PasteBot&lt;/a&gt; - 强大的剪贴板管理器。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;菜单栏工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/beardedspice/beardedspice" target="_blank"&gt;BeardedSpice&lt;/a&gt; - 允许您使用 Mac 键盘上的媒体键控制基于Web的媒体播放器（SoundCloud，YouTube 等）和一些本机应用程序。 &lt;a href="https://github.com/beardedspice/beardedspice" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.macbartender.com" target="_blank"&gt;Bartender&lt;/a&gt; - 组织或隐藏Mac上的菜单栏图标。&lt;/li&gt; &lt;li&gt;&lt;a href="https://getbitbar.com/" target="_blank"&gt;BitBar&lt;/a&gt; - 支持使用各种语言将信息展示到 Mac OS 的菜单栏。&lt;a href="https://github.com/matryer/bitbar" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Moneypulation/iGlance" target="_blank"&gt;iGlance&lt;/a&gt; - 状态栏的系统监视器。 &lt;a href="https://github.com/Moneypulation/iGlance" target="_blank"&gt; 开源地址&lt;/a&gt; &lt;a href="https://github.com/Moneypulation/iGlance" target="_blank"&gt;&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.mowglii.com/itsycal/" target="_blank"&gt;Itsycal&lt;/a&gt; - 一款简洁实用的开源日历工具。&lt;a href="https://github.com/sfsam/itsycal" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://vanilla.matthewpalmer.net" target="_blank"&gt;Vanilla&lt;/a&gt; - 隐藏系统菜单栏。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;待办事项工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://www.2doapp.com/" target="_blank"&gt;2Do&lt;/a&gt; - 比较好的 TODO 应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.shauninman.com/archive/2016/10/20/day_o_2_mac_menu_bar_clock" target="_blank"&gt;Day-O 2&lt;/a&gt; - 菜单日历更换内置日历。&lt;/li&gt; &lt;li&gt;&lt;a href="https://flexibits.com/fantastical" target="_blank"&gt;Fantastical&lt;/a&gt; - 日历应用程序，你将管理好生活。&lt;/li&gt; &lt;li&gt;&lt;a href="https://masterbuilders.io" target="_blank"&gt;Focus&lt;/a&gt; - 一个漂亮的番茄工作法为基础的时间管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://todo.microsoft.com/" target="_blank"&gt;Microsoft To-Do&lt;/a&gt; - 任务管理工具微软出品。&lt;/li&gt; &lt;li&gt;&lt;a href="https://nozbe.com" target="_blank"&gt;Nozbe&lt;/a&gt; - 适用于个人和团队的强大GTD应用程序，支持每个Apple设备。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/omnifocus/" target="_blank"&gt;OmniFocus&lt;/a&gt; - 由 OmniGroups 制作的 Nice GTD 应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.taskade.com" target="_blank"&gt;Taskade&lt;/a&gt; - 实时协作编辑器，协作简历任务管理器，大纲和笔记。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.taskpaper.com/" target="_blank"&gt;TaskPaper&lt;/a&gt; - 漂亮的纯文本任务列表。&lt;/li&gt; &lt;li&gt;&lt;a href="https://culturedcode.com/things/" target="_blank"&gt;Things&lt;/a&gt; - 令人愉快且易于使用的任务管理器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://todoist.com/mac" target="_blank"&gt;Todoist&lt;/a&gt; - 跨平台的任务管理器与移动应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.wunderlist.com/?ncr=1" target="_blank"&gt;Wunderlist&lt;/a&gt; - 奇妙清单跨平台的任务管理器与移动应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.dida365.com/" target="_blank"&gt;滴答清单&lt;/a&gt; - 轻便且强大的跨平台任务管理应用。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;系统相关工具&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://itunes.apple.com/cn/app/amphetamine/id937984704" target="_blank"&gt;Amphetamine&lt;/a&gt; - 覆盖您的节能设置并让您的Mac保持清醒状态。&lt;/li&gt; &lt;li&gt;&lt;a href="https://cleanerone.trendmicro.com/ad-block-one-for-mac/?utm_source=github&amp;amp;utm_medium=referral&amp;amp;utm_campaign=githubproject" target="_blank"&gt;AdBlock One&lt;/a&gt; - 适用于MacOS/iOS的免费广告拦截器 停止在Safari中看到烦人的广告。更快地打开网站。更安全地浏览网页。&lt;/li&gt; &lt;li&gt;&lt;a href="http://freemacsoft.net/appcleaner/" target="_blank"&gt;AppCleaner&lt;/a&gt; - 一个小应用程序，让你彻底卸载不需要的应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="http://onnati.net/apptrap/" target="_blank"&gt;AppTrap&lt;/a&gt; - 删除APP的同时移除文件。 &lt;a href="https://github.com/kvijayan/AppTrap" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/toy/blueutil" target="_blank"&gt;blueutil&lt;/a&gt; - 命令行蓝牙控制工具，可以配合&lt;a href="https://www.bernhard-baehr.de/" target="_blank"&gt;SleepWatcher&lt;/a&gt;实现MacBook合盖瞬间关闭蓝牙，开盖自动打开蓝牙。这在使用蓝牙耳机时尤其有用。&lt;a href="https://github.com/toy/blueutil" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/cn/app/apple-store/id1133028347?pt=444218&amp;amp;ct=GitHub&amp;amp;mt=8" target="_blank"&gt;Cleaner One&lt;/a&gt; - 多合一磁盘清理管理器：清理您的 Mac 并优化其性能，立即运行快速扫描以验证什么占用了您的存储空间。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/waylybaye/XcodeCleaner" target="_blank"&gt;Cleaner for Xcode&lt;/a&gt; - Xcode 的清理工具，清理几十G应该不是问题。&lt;a href="https://github.com/waylybaye/XcodeCleaner" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://coolantformac.com" target="_blank"&gt;Coolant&lt;/a&gt; - 这是能让你知道什么应用程序造成你 CPU100% 让 Mac 电脑过热电池耗尽的菜单应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.coconut-flavour.com/coconutbattery/" target="_blank"&gt;coconutBattery&lt;/a&gt; - 显示Mac中有关电池的实时信息。&lt;/li&gt; &lt;li&gt;&lt;a href="https://daisydiskapp.com/" target="_blank"&gt;DaisyDisk&lt;/a&gt; - 磁盘空间使用扫描工具。&lt;/li&gt; &lt;li&gt;&lt;a href="http://fruitjuiceapp.com" target="_blank"&gt;FruitJuice&lt;/a&gt; - 会让你知道每天保持不插电的时间，以保持你的电池健康。&lt;/li&gt; &lt;li&gt;&lt;a href="https://gfx.io/" target="_blank"&gt;gfxCardStatus&lt;/a&gt; - 控制Mac独立显卡与集成显卡之间的切换。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.smartisan.com/apps/handshaker" target="_blank"&gt;HandShaker&lt;/a&gt; -  Mac 电脑上也可以方便自如地管理您在 Android 手机中的内容。&lt;/li&gt; &lt;li&gt;&lt;a href="http://zythum.sinaapp.com/youkuhtml5playerbookmark/" target="_blank"&gt;HTML5 Player&lt;/a&gt; - Chrome 插件解决中国视频网站播放视频电脑发热的情况。&lt;/li&gt; &lt;li&gt;&lt;a href="https://bjango.com/mac/istatmenus/" target="_blank"&gt;iStat Menus&lt;/a&gt; - 菜单栏上的高级 Mac 系统监视器。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/Chris911/iStats" target="_blank"&gt;iStats&lt;/a&gt; - iStats 是一个可以让你快速查看电脑 CPU 温度，磁盘转速和电池等信息的命令行工具。&lt;a href="https://github.com/Chris911/iStats" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/brianmichel/Juice" target="_blank"&gt;Juice&lt;/a&gt; - 让电池显示更有趣 &lt;a href="https://github.com/brianmichel/Juice" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/newmarcel/KeepingYouAwake" target="_blank"&gt;KeepingYouAwake&lt;/a&gt; - 替代咖啡因，更好地支持Mac中的暗模式。 &lt;a href="https://github.com/newmarcel/KeepingYouAwake" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.monityapp.com/" target="_blank"&gt;Monity&lt;/a&gt; - 帮助用户实时监控系统的一款非常漂亮的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://enjoygineering.com/mounty/" target="_blank"&gt;Mounty&lt;/a&gt; - NTFS 分区读写组件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://nitroshare.net/" target="_blank"&gt;NitroShare&lt;/a&gt; - 跨平台网络文件传输应用程序。 &lt;a href="https://github.com/nitroshare/nitroshare-desktop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.titanium.free.fr/" target="_blank"&gt;OnyX&lt;/a&gt; - 多功能实用工具来验证磁盘和文件，运行清洁和系统维护任务，配置隐藏选项等。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.omnigroup.com/more" target="_blank"&gt;OmniDiskSweeper&lt;/a&gt; - 磁盘空间使用扫描工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.paragon-software.com/home/ntfs-mac/" target="_blank"&gt;Paragon NTFS&lt;/a&gt; - 在 Mac OS X 中完全读写、修改、访问 Windows NTFS 硬盘、U 盘等外接设备的文件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://portingkit.com/" target="_blank"&gt;Porting Kit&lt;/a&gt; - 在Mac中安装Windows®游戏。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.bernhard-baehr.de/" target="_blank"&gt;SleepWatcher&lt;/a&gt; - 可以在MacBook合盖和开盖时执行自定义脚本，比如开关蓝牙等。可以通过&lt;code&gt;homebrew&lt;/code&gt;安装。&lt;a href="https://www.bernhard-baehr.de/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/hholtmann/smcFanControl" target="_blank"&gt;smcFanControl&lt;/a&gt; - 短小精悍的风扇转速温控软件，可以预设两档风扇最低转速，方便在不同工作负载间人工强制切换。因为只是限制风扇最低速度，所以系统原生温控调速不会完全失效。&lt;a href="https://github.com/hholtmann/smcFanControl" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://codinn.com/products/ssh-tunnel/" target="_blank"&gt;SSH Tunnel&lt;/a&gt; - 管理你的 SSH。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.tunabellysoftware.com/tgpro/" target="_blank"&gt;TG Pro&lt;/a&gt; - 温度监控，风扇控制和硬件诊断，帮助您保持 Mac的 凉爽和健康。&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.tuxera.com/products/tuxera-ntfs-for-mac/" target="_blank"&gt;Tuxera NTFS&lt;/a&gt; - Mac 上的 NTFS 文件系统驱动。&lt;/li&gt; &lt;li&gt;&lt;a href="https://lemon.qq.com/" target="_blank"&gt;腾讯柠檬清理&lt;/a&gt; - 一款免费的Mac系统清理软件，替代原来的Mac电脑管家，腾讯出品。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;窗口管理&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://ianyh.com/amethyst/" target="_blank"&gt;Amethyst&lt;/a&gt; - 窗口管理器（自动保持窗口大小的窗口）。&lt;a href="https://github.com/ianyh/Amethyst" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://folivora.ai/bettersnaptool/" target="_blank"&gt;BetterSnapTool&lt;/a&gt; - 窗口管理工具，可通过快捷键或窗口拖动快速实现分屏。&lt;/li&gt; &lt;li&gt;&lt;a href="https://contexts.co/" target="_blank"&gt;Contexts&lt;/a&gt;- 提供比 Mac 原生 Dock 更强大功能尤其在你有多个屏幕的时候,它可以帮助你更快捷切换。&lt;/li&gt; &lt;li&gt;&lt;a href="http://mizage.com/divvy/" target="_blank"&gt;Divvy&lt;/a&gt; - 凭借其惊人的 Divvy Grid 系统，窗口管理处于最佳状态。&lt;/li&gt; &lt;li&gt;&lt;a href="https://mightymac.app/intellidock/" target="_blank"&gt;IntelliDock&lt;/a&gt; - 自动隐藏 Dock。&lt;/li&gt; &lt;li&gt;&lt;a href="http://manytricks.com/moom/" target="_blank"&gt;Moom&lt;/a&gt; - 多任务多窗口的软件。&lt;/li&gt; &lt;li&gt;&lt;a href="http://magnet.crowdcafe.com/" target="_blank"&gt;Magnet&lt;/a&gt; - 一个窗口管理器，可以保持工作空间的组织。&lt;/li&gt; &lt;li&gt;&lt;a href="https://lowtechguys.com/rcmd/" target="_blank"&gt;rcmd&lt;/a&gt; - 使用 &lt;kbd&gt;⌘ Right Command&lt;/kbd&gt; 键根据名称切换应用程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/fikovnik/ShiftIt" target="_blank"&gt;ShiftIt&lt;/a&gt; - 窗口位置和大小管理软件。 &lt;a href="https://github.com/fikovnik/ShiftIt" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/jigish/slate" target="_blank"&gt;Slate&lt;/a&gt; - 窗口管理器，可用 JavaScript 写配置。&lt;a href="https://github.com/jigish/slate" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.irradiatedsoftware.com/sizeup/" target="_blank"&gt;SizeUp&lt;/a&gt; - 强大的，以键盘为中心的窗口管理。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.spectacleapp.com" target="_blank"&gt;Spectacle&lt;/a&gt; - 简单的移动和调整大小的窗口，和可定制的键盘快捷键。 &lt;a href="https://github.com/eczarny/spectacle" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://totalspaces.binaryage.com/" target="_blank"&gt;Total Spaces&lt;/a&gt; - 像 ubuntu 一样提供窗口管理，为工作区创建热键，使您可以轻松移动。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;密码管理&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://1password.com/" target="_blank"&gt;1password&lt;/a&gt; - 跨平台帐号密码管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://authy.com" target="_blank"&gt;Authy&lt;/a&gt; - 双因素身份验证令牌管理器，可在您的设备上进行备份和同步。&lt;/li&gt; &lt;li&gt;&lt;a href="https://bitwarden.com" target="_blank"&gt;Bitwarden&lt;/a&gt; - 适用于Mac OS，iOS和浏览器的开源密码管理工具。 &lt;a href="https://github.com/bitwarden" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://buttercup.pw/" target="_blank"&gt;Buttercup&lt;/a&gt; - 跨平台密码管理器&lt;a href="https://github.com/buttercup/buttercup-desktop" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.dashlane.com" target="_blank"&gt;Dashlane&lt;/a&gt; - 基于云的密码管理器，拥有屡获殊荣的设计。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.enpass.io/" target="_blank"&gt;Enpass&lt;/a&gt; - 具有云集成的跨平台密码管理工具。&lt;/li&gt; &lt;li&gt;&lt;a href="https://keeweb.info/" target="_blank"&gt;Keeweb&lt;/a&gt; - 与 KeePass 兼容的免费跨平台密码管理器。 &lt;a href="https://github.com/keeweb/keeweb" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.lastpass.com/" target="_blank"&gt;LastPass&lt;/a&gt; - 密码管理器和安全的数字笔记。&lt;/li&gt; &lt;li&gt;&lt;a href="https://macpass.github.io/" target="_blank"&gt;MacPass&lt;/a&gt; - 密码管理器。&lt;a href="https://github.com/mstarke/MacPass" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.remembear.com/" target="_blank"&gt;RememBear&lt;/a&gt; - 治愈系密码管理工具。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Finder&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://fman.io" target="_blank"&gt;fman&lt;/a&gt; - 先进的双窗口文件管理器，拥有很多特性。&lt;/li&gt; &lt;li&gt;&lt;a href="http://binarynights.com/forklift/" target="_blank"&gt;ForkLift&lt;/a&gt; - 先进的双窗口文件管理器和文件传输客户端。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.noodlesoft.com" target="_blank"&gt;Hazel&lt;/a&gt; - 设计精美的自动文件管理软件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/vanshg/MacAssistant/releases" target="_blank"&gt;MacAssistant&lt;/a&gt; - Google 助手 &lt;a href="https://github.com/vanshg/MacAssistant" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.cocoatech.com/pathfinder/" target="_blank"&gt;Path Finder&lt;/a&gt; - 强大的 Finder 替代者，拥有很多特性。&lt;/li&gt; &lt;li&gt;&lt;a href="https://github.com/sindresorhus/quick-look-plugins" target="_blank"&gt;Quicklook-Plugins&lt;/a&gt; - Finder 快速预览文件插件。&lt;/li&gt; &lt;li&gt;&lt;a href="https://qspace.awehunt.com" target="_blank"&gt;QSpace&lt;/a&gt; - 一款简洁高效的多视图文件管理器。&lt;/li&gt; &lt;li&gt;&lt;a href="http://totalfinder.binaryage.com/" target="_blank"&gt;TotalFinder&lt;/a&gt; - 强大的 Finder 替代者，界面风格像 Chrome。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.trankynam.com/xtrafinder/" target="_blank"&gt;XtraFinder&lt;/a&gt; - 给 Finder 添加有用的新特性。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;远程协助&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://rustdesk.com/" target="_blank"&gt;RustDesk&lt;/a&gt; - 又一个远程桌面软件。 &lt;a href="https://github.com/rustdesk/rustdesk" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://anydesk.com" target="_blank"&gt;AnyDesk&lt;/a&gt; 是一款远程控制跨多平台的程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://apps.apple.com/us/app/microsoft-remote-desktop/id1295203466?mt=12" target="_blank"&gt;Microsoft Remote Desktop&lt;/a&gt; - 微软官方的远程桌面连接工具(国区App store没有上架,&lt;a href="https://go.microsoft.com/fwlink/?linkid=868963" target="_blank"&gt;下载地址&lt;/a&gt;)。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.realvnc.com" target="_blank"&gt;RealVNC&lt;/a&gt; 是一款免费的远程控制跨多平台的程序。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.teamviewer.com" target="_blank"&gt;TeamViewer&lt;/a&gt; - 远程协助及在线协作和会议功能的软件，商业软件个人使用免费。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;QuickLook插件&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;List of useful &lt;a href="http://en.wikipedia.org/wiki/Quick_Look" target="_blank"&gt;Quick Look&lt;/a&gt; plugins for developers.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;em&gt;使用 &lt;a href="https://github.com/phinze/homebrew-cask" target="_blank"&gt;Homebrew Cask&lt;/a&gt; 将通过命令安装即为简单。开发人员使用的&lt;a href="http://en.wikipedia.org/wiki/Quick_Look" target="_blank"&gt;Quick Look&lt;/a&gt;插件列表。如果手动安装，你可将下载的 &lt;code&gt;.qlgenerator&lt;/code&gt; 文件移动到 &lt;code&gt;~/Library/QuickLook&lt;/code&gt; 运行 &lt;code&gt;qlmanage -r&lt;/code&gt;&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="https://github.com/whomwah/qlstephen" target="_blank"&gt;QuicklookStephen&lt;/a&gt; - 可以让您查看没有文件扩展名的纯文本文件，如 README、INSTALL、Capfile、CHANGELOG...&lt;code&gt;brew install --cask install qlstephen&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;第三方应用市场APP&lt;/h2&gt; &lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/jaywcjlove/awesome-mac/issues/17" target="_blank"&gt;这里讨论盗版问题或者提供黑名单？&lt;/a&gt;，拒绝盗版从我做起，欢迎大家监督。&lt;/em&gt;&lt;/p&gt; &lt;h3&gt;正版&lt;/h3&gt; &lt;p&gt;&lt;em&gt;这里只提供正版软件购买下载的应用商店。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://caskroom.github.io" target="_blank"&gt;Homebrew Cask&lt;/a&gt; - 基于&lt;a href="https://brew.sh/" target="_blank"&gt;Homebrew&lt;/a&gt;扩展的，通过命令行安装 Mac GUI 软件的工具。&lt;a href="https://github.com/caskroom/homebrew-cask" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://brew.sh/" target="_blank"&gt;Homebrew&lt;/a&gt; - 体验通过命令行安装 Mac 软件的工具(大部分是命令行工具)。&lt;a href="https://github.com/Homebrew/brew/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.macupdate.com/" target="_blank"&gt;MacUpdate Desktop&lt;/a&gt; - 管理/更新/下载 App，跟踪优惠信息。&lt;/li&gt; &lt;li&gt;&lt;a href="https://www.macports.org/" target="_blank"&gt;MacPorts&lt;/a&gt; - 一个软件包管理工具，可用于简化 OS X 和 Darwin 操作系统内软件的安装。&lt;a href="https://github.com/macports/" target="_blank"&gt; 开源地址&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="https://setapp.com" target="_blank"&gt;Setapp&lt;/a&gt; - MacPaw 推出的订阅制付费 App 平台服务。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;应用商店黑名单&lt;/h3&gt; &lt;p&gt;&lt;em&gt;第三方应用市场APP黑名单，存在盗版软件传播和下载，拒绝盗版从我做起，欢迎大家监督它们。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;~~&lt;a href="http://mac.xunlei.com/app/" target="_blank"&gt;迅雷Thunder Store&lt;/a&gt;~~ - 迅雷 Thunder for Mac 带应用市场。&lt;/li&gt; &lt;li&gt;~~&lt;a href="http://box.macx.cn/" target="_blank"&gt;Mac软件宝箱&lt;/a&gt;~~ - Macx 推出软件宝箱。&lt;/li&gt; &lt;li&gt;~~&lt;a href="http://www.machunter.net/" target="_blank"&gt;MacHunter&lt;/a&gt;~~ - Mac 应用市场。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Mac软件下载网站&lt;/h2&gt; &lt;p&gt;&lt;em&gt;这里主要是推荐一些软件下载的网站，还有一些Mac OSX软件分享网站&lt;/em&gt;&lt;/p&gt; &lt;h3&gt;正版/介绍&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;App Shopper：&lt;a href="http://appshopper.com/" target="_blank"&gt;http://appshopper.com/&lt;/a&gt;&lt;/li&gt; &lt;li&gt;MacUpdate：&lt;a href="https://www.macupdate.com/" target="_blank"&gt;https://www.macupdate.com/&lt;/a&gt;&lt;/li&gt; &lt;li&gt;少数派：&lt;a href="http://sspai.com/tag/Mac" target="_blank"&gt;http://sspai.com/tag/Mac&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Mac玩儿法：&lt;a href="http://www.waerfa.com" target="_blank"&gt;http://www.waerfa.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;腾讯柠檬精选：&lt;a href="https://lemon.qq.com/lab/" target="_blank"&gt;https://lemon.qq.com/lab/&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;盗版软件下载网站黑名单&lt;/h3&gt; &lt;p&gt;&lt;em&gt;上面有大量的开源软件或者免费软件，拒绝盗版从我做起，下面被删除的网站提供大量破解软件下载，欢迎大家监督它们。&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;玩转苹果：~~&lt;code&gt;http://www.ifunmac.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;AppKed：~~&lt;code&gt;http://www.macbed.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;appaddict：~~&lt;code&gt;https://www.appaddict.org/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac精品软件：~~&lt;code&gt;http://xclient.info/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;MacWk：~~&lt;code&gt;https://macwk.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;MacPeers：~~&lt;code&gt;https://www.macpeers.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac毒：~~&lt;code&gt;https://www.macdo.cn&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Macx：~~&lt;code&gt;https://www.macx.cn/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac软件下载站：~~&lt;code&gt;http://www.pshezi.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;MacPeers：~~&lt;code&gt;http://www.macpeers.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac志：~~&lt;code&gt;http://www.isofts.org&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac软件分享：~~&lt;code&gt;http://www.waitsun.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;MacSky苹果软件园：~~&lt;code&gt;http://www.macsky.net/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Softasm：~~&lt;code&gt;https://softasm.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;Mac破解软件：~~&lt;code&gt;https://www.macappstore.net/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;卡卡源：~~&lt;code&gt;http://www.kkroot.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;苹果软件园：~~&lt;code&gt;http://www.maczapp.com&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;马可菠萝：~~&lt;code&gt;http://www.macbl.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;极致分享：~~&lt;code&gt;https://alltoshare.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;麦克社：~~&lt;code&gt;http://www.macshe.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;未来软件园：~~&lt;code&gt;http://www.orsoon.com/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;腾牛网：~~&lt;code&gt;http://www.qqtn.com/mac/r_17_1.html&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;未来软件园：~~&lt;code&gt;http://www.orsoon.com/mac/&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;威锋网：~~&lt;code&gt;https://bbs.feng.com/forum.php?mod=forumdisplay&amp;amp;fid=19&amp;amp;page=&lt;/code&gt;~~&lt;/li&gt; &lt;li&gt;MAC萌新网：~~&lt;code&gt;https://www.macxin.com&lt;/code&gt;~~&lt;/li&gt; &lt;/ul&gt; &lt;!--end--&gt;</content:encoded>
      <pubDate>Sun, 20 Sep 2020 10:19:00 GMT</pubDate>
    </item>
    <item>
      <title>IDEA快捷键</title>
      <link>https://maruifu.cn/article/151</link>
      <content:encoded>&lt;h1&gt;（Win + Linux）快捷键&lt;/h1&gt; &lt;h2&gt;说明&lt;/h2&gt; &lt;p&gt;IntelliJ IDEA 的便捷操作性，快捷键的功劳占了一大半，对于各个快捷键组合请认真对待。IntelliJ IDEA 本身的设计思维是提倡键盘优先于鼠标的，所以各种快捷键组合层出不穷，对于快捷键设置也有各种支持，对于其他 IDE 的快捷键组合也有预设模板进行支持。&lt;/p&gt; &lt;p&gt;关于各个快捷键的频率分类上可能每个人都有各自的看法，下面的整理也只是以我个人的使用习惯来划分的，而我应该是可以代表某一部分小众人员。但是我个人还是强烈建议你可以在我的基础上整理一份属于你的快捷键目录（删除掉多余的字眼，只保留快捷键内容），本篇文章也只是起到一个工具和引子的作用。&lt;/p&gt; &lt;p&gt;对于下面各个快捷键的使介绍描述也许用我个人语言翻译起来不够准确或是不全面，且在不同的文件类型上按出来的效果也可能结果不太一样,所以 &lt;strong&gt;强烈建议&lt;/strong&gt; 你自己把各个快捷键都亲自操作下体会下各个快捷键的实际用法。&lt;/p&gt; &lt;h2&gt;前提&lt;/h2&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;IntelliJ IDEA 官方出的学习辅助插件：IDE Features Trainer：&lt;a href="https://plugins.jetbrains.com/plugin/8554?pr=idea" target="_blank"&gt;https://plugins.jetbrains.com/plugin/8554?pr=idea&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h2&gt;Ctrl&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本查找 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本替换 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;撤销 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Y&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标所在行 或 删除选中的行 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;X&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;剪切光标所在行 或 剪切选择内容&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制光标所在行 或 复制选择内容&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;D&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制光标所在行 或 复制选择内容，并把复制内容插入光标位置下面 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展选中范围 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示最近打开的文件记录列表 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入的 &lt;strong&gt;类名&lt;/strong&gt; 查找类文件 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;G&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件跳转到指定行处&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;插入自定义动态代码模板 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;P&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;方法参数提示显示 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Q&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标所在的变量 / 类名 / 方法名等上面（也可以在提示补充的时候按），显示文档内容&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前往当前光标所在的方法的父类的方法 / 接口定义 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;进入光标所在的方法/变量的接口或是定义处，等效于 &lt;code&gt;Ctrl + 左键单击&lt;/code&gt;  &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;K&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;版本控制提交项目，需要此项目有加入到版本控制才可用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;版本控制更新项目，需要此项目有加入到版本控制才可用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;H&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示当前类的层次结构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择可重写的方法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;I&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择可继承的方法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;+&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;-&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;注释光标所在行代码，会根据当前不同文件类型使用不同的注释符号 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标到当前所在代码的花括号开始位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标到当前所在代码的花括号结束位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在光标所在的错误代码处显示错误信息 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;调转到所选中的词的下一个引用位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;关闭当前编辑文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F8&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，设置光标当前行为断点，如果当前已经是断点则去掉断点&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;执行 Make Project 操作&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中文件 / 文件夹，使用助记符设定 / 取消书签 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出当前文件结构层，可以在弹出的层上直接输入，进行筛选&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑窗口切换，如果在切换的过程又加按上delete，则是关闭对应选中的窗口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;End&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳到文件尾&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Home&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳到文件头&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;基础代码补全，默认在 Windows 系统上被输入法占用，需要进行修改，建议修改为 &lt;code&gt;Ctrl + 逗号&lt;/code&gt; &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标后面的单词或是中文句 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;BackSpace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标前面的单词或是中文句 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;定位到对应数值的书签位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件标题上，弹出该文件路径 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;光标定位&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;按 Ctrl 不要松开，会显示光标所在的类信息摘要&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的左侧开头位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的右侧开头位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向前效果 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向后效果 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;`&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示版本控制常用操作菜单弹出层 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Q&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出一个提示，显示当前类的声明 / 上下文信息&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示当前文件选择目标弹出层，弹出层中有很多目标可以进行选择 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对于前面页面，显示各类浏览器打开目标选择弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中文本，逐个往下查找相同文本，并高亮显示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查找光标所在的方法 / 变量 / 类被调用的地方&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F8&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 的状态下，选中对象，弹出可输入计算表达式调试框，查看该输入内容的调试结果&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Home&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;定位 / 显示到当前文件的 &lt;code&gt;Navigation Bar&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;IntelliJ IDEA 根据光标所在问题，提供快速修复选择，光标放在的位置不同提示的结果也不同 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Insert&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码自动生成，如生成对象的 set / get 方法，构造函数，toString() 等 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;按切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的前一个方法名位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的后一个方法名位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示对应数值的选项卡，其中 1 是 Project 用得最多 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;如果有外部文档可以连接外部文档&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到上一个高亮错误 或 警告位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在查找模式下，查找匹配上一个&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对当前打开的文件，使用新Windows窗口打开，旧窗口保留&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F6&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对文件 / 文件夹 重命名&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，智能步入。断点所在行上有多个方法调用，会弹出进入哪个方法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F8&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，跳出，表现出来的效果跟 &lt;code&gt;F9&lt;/code&gt; 一样&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;等效于点击工具栏的 &lt;code&gt;Debug&lt;/code&gt; 按钮&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F10&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;等效于点击工具栏的 &lt;code&gt;Run&lt;/code&gt; 按钮&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出书签显示层 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消缩进 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;隐藏当前 或 最后一个激活的工具窗口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;End&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中光标到当前行尾位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Home&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中光标到当前行头位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;开始新一行。光标所在行下空出一行，光标定位到新行位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件名上按此快捷键，可以关闭当前打开文件 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;滚轮前后滚动&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前文件的横向滚动轴滚动 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;格式化代码，可以对当前文件和整个包目录使用 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;优化导入的类，可以对当前文件和整个包目录使用 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;I&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标所在行 或 选中部分进行自动代码缩进，有点类似格式化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码弹出环绕选项弹出层 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出模板选择窗口，将选定的代码加入动态模板中&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;H&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;调用层次&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在某个调用的方法名上使用会跳到具体的实现处，可以跳过接口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;重构-快速提取常量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;重构-快速提取成员变量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;重构-快速提取变量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Y&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;同步、刷新&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开 IntelliJ IDEA 系统设置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示使用的地方。寻找被该类或是变量被调用的地方，用弹出框的方式找出来&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换全屏模式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标所在行上空出一行，光标定位到新行 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Home&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出跟当前文件有关联的文件弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;类名自动完成&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上一个操作的地方 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前进到上一个操作的地方 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在查找模式下，跳到上个查找的文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在查找模式下，跳到下个查找的文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右括号（]）&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开多个项目的情况下，切换下一个项目窗口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左括号（[）&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开多个项目的情况下，切换上一个项目窗口&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容查找整个项目 或 指定目录内文件 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容替换对应内容，范围为整个项目 或 指定目录内文件 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动将下一行合并到当前行末尾 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消撤销 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式取消选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展取消选中范围 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;通过文件名定位 / 打开文件 / 目录，打开目录需要在输入的内容后面多加一个正斜杠 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码进行大 / 小写轮流转换 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对当前类生成单元测试类，如果已经存在的单元测试类则可以进行选择 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制当前文件磁盘路径到剪贴板 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出缓存的最近拷贝的内容管理器弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示最近修改的文件列表的弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;H&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示方法层次结构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到类型声明处 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;I&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;快速查看光标所在的方法 或 类的定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;A&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查找动作 / 设置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码块注释 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的顶部中括号位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的底部中括号位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;+&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开所有代码 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;-&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠所有代码 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;高亮显示所有该选中文本，按Esc高亮消失 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F8&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，指定断点进入条件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编译选中的文件 / 包 / Module&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑器最大化 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;智能代码提示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动结束代码，行末自动添加分号 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Backspace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上次修改的地方 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;快速添加指定数值的书签 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;把光标放在某个类变量上，按此快捷键可以直接定位到该类中 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的左侧开头位置，同时选中该单词 / 中文句 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的右侧开头位置，同时选中该单词 / 中文句 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到上一个方法前面，调整方法排序 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到下一个方法前面，调整方法排序 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择 / 添加 task &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示添加到收藏夹弹出层 / 添加到收藏夹&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查看最近操作项目的变化情况列表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;I&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查看项目当前文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，下一步，进入当前方法体内，如果方法体还有方法，则会进入该内嵌的方法中，依此循环进入&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出 &lt;code&gt;Debug&lt;/code&gt;  的可选择菜单&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F10&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出 &lt;code&gt;Run&lt;/code&gt;  的可选择菜单&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键双击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择被双击的单词 / 中文句，按住不放，可以同时选择其他单词 / 中文句 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向上移动 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向下移动 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;无格式黏贴 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前往指定的变量 / 方法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开当前项目设置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制参考信息&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;其他&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到下一个高亮错误 或 警告位置 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在查找模式下，定位到下一个匹配处&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑源 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，进入下一步，如果当前行断点是一个方法，则进入当前方法体内，如果该方法体还有方法，则不会进入该内嵌的方法中&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F8&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，进入下一步，如果当前行断点是一个方法，则不进入当前方法体内&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在 Debug 模式下，恢复程序运行，但是如果该断点下面代码还有断点则停在下一个断点上&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;添加书签 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;回到前一个工具窗口 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;缩进 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;从工具窗口进入代码文件窗口 &lt;code&gt;（必备）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;连按两次Shift&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出 &lt;code&gt;Search Everywhere&lt;/code&gt; 弹出层&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;官网快捷键资料&lt;/h2&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;Windows / Linux：&lt;a href="https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard.pdf" target="_blank"&gt;https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard.pdf&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Mac OS X：&lt;a href="https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard_Mac.pdf" target="_blank"&gt;https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard_Mac.pdf&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h2&gt;第三方快捷键资料&lt;/h2&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;来自 eta02913：&lt;a href="http://xinyuwu.iteye.com/blog/1005454" target="_blank"&gt;http://xinyuwu.iteye.com/blog/1005454&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h1&gt;Mac 快捷键&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;根据官方pdf翻译：&lt;a href="https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard_Mac.pdf" target="_blank"&gt;https://www.jetbrains.com/idea/docs/IntelliJIDEA_ReferenceCard_Mac.pdf&lt;/a&gt;&lt;/li&gt; &lt;li&gt;在 IntelliJ IDEA 中有两个 Mac 版本的快捷键，一个叫做：Mac OS X，一个叫做：Mac OS X 10.5+&lt;/li&gt; &lt;li&gt;目前都是用：Mac OS X 10.5+&lt;/li&gt; &lt;li&gt;有两套的原因：&lt;a href="https://intellij-support.jetbrains.com/hc/en-us/community/posts/206159109-Updated-Mac-OS-X-keymap-Feedback-needed" target="_blank"&gt;https://intellij-support.jetbrains.com/hc/en-us/community/posts/206159109-Updated-Mac-OS-X-keymap-Feedback-needed&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;建议将 Mac 系统中与 IntelliJ IDEA 冲突的快捷键取消或更改，不建议改 IntelliJ IDEA 的默认快捷键。&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;Mac 键盘符号&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;图标&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;⌘&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇧&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇪&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Caps Lock&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; = &lt;kbd&gt;Alt&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌃&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↩&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌫&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌦&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↑&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;上箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↓&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;下箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;←&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;左箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;→&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;右箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇞&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; = &lt;kbd&gt;Page Up&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇟&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; = &lt;kbd&gt;Page Down&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Home&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;←&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;End&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;→&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt; = &lt;kbd&gt;右制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇤&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Tab&lt;/kbd&gt; = &lt;kbd&gt;左制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⎋&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Esc&lt;/kbd&gt; = &lt;kbd&gt;Escape&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⏏&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;电源开关键&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4&gt;Editing（编辑）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Control + Space&lt;/code&gt; 基本的代码补全（补全任何类、方法、变量）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + Space&lt;/code&gt; 智能代码补全（过滤器方法列表和变量的预期类型）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + Enter&lt;/code&gt; 自动结束代码，行末自动添加分号&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + P&lt;/code&gt; 显示方法的参数信息&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + J&lt;/code&gt; 显示当前位置的变量、方法的 Documentation 内容&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + J&lt;/code&gt; 快速查看文档&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + F1&lt;/code&gt; 查看外部文档（在某些代码上会触发打开浏览器显示相关文档）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + 鼠标放在代码上&lt;/code&gt; 显示代码简要信息&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F1&lt;/code&gt; 在错误或警告处显示具体描述信息&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + N, Control + Enter, Control + N&lt;/code&gt; 生成代码（getter、setter、构造函数、hashCode/equals,toString）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + O&lt;/code&gt; 覆盖方法（重写父类方法）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + I&lt;/code&gt; 实现方法（实现接口中的方法）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + T&lt;/code&gt; 包围代码（使用if..else, try..catch, for, synchronized等包围选中的代码）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + /&lt;/code&gt; 注释/取消注释与行注释&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + /&lt;/code&gt; 注释/取消注释与块注释&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + 方向键上&lt;/code&gt; 连续选中代码块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + 方向键下&lt;/code&gt; 减少当前选中的代码块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + Q&lt;/code&gt; 显示上下文信息&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Enter&lt;/code&gt; 显示意向动作和快速修复代码&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + L&lt;/code&gt; 格式化代码&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Option + O&lt;/code&gt; 优化import&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Option + I&lt;/code&gt; 自动缩进线&lt;/li&gt; &lt;li&gt;&lt;code&gt;Tab / Shift + Tab&lt;/code&gt; 缩进代码 / 反缩进代码&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + X&lt;/code&gt; 剪切当前行或选定的块到剪贴板&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + C&lt;/code&gt; 复制当前行或选定的块到剪贴板&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + V&lt;/code&gt; 从剪贴板粘贴&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + V&lt;/code&gt; 从最近的缓冲区粘贴&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + D&lt;/code&gt; 复制当前行或选定的块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Delete&lt;/code&gt; 删除当前行或选定的块的行&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + J&lt;/code&gt; 智能的将代码拼接成一行&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Enter&lt;/code&gt; 智能的拆分拼接的行&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + Enter&lt;/code&gt; 开始新的一行&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + U&lt;/code&gt; 大小写切换&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + ] / Command + Shift + [&lt;/code&gt; 选择直到代码块结束/开始&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Fn + Delete&lt;/code&gt; 删除到单词的末尾&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Delete&lt;/code&gt; 删除到单词的开头&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + 加号 / Command + 减号&lt;/code&gt; 展开 / 折叠代码块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + 加号&lt;/code&gt; 展开所以代码块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + 减号&lt;/code&gt; 折叠所有代码块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + W&lt;/code&gt; 关闭活动的编辑器选项卡&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Search/Replace（查询/替换）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Double Shift&lt;/code&gt; 查询任何东西&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F&lt;/code&gt; 文件内查找&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + G&lt;/code&gt; 查找模式下，向下查找&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + G&lt;/code&gt; 查找模式下，向上查找&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + R&lt;/code&gt; 文件内替换&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F&lt;/code&gt; 全局查找（根据路径）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + R&lt;/code&gt; 全局替换（根据路径）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + S&lt;/code&gt; 查询结构（Ultimate Edition 版专用，需要在Keymap中设置）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + M&lt;/code&gt; 替换结构（Ultimate Edition 版专用，需要在Keymap中设置）&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Usage Search（使用查询）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Option + F7 / Command + F7&lt;/code&gt; 在文件中查找用法 / 在类中查找用法&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F7&lt;/code&gt; 在文件中突出显示的用法&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + F7&lt;/code&gt; 显示用法&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Compile and Run（编译和运行）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + F9&lt;/code&gt; 编译Project&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F9&lt;/code&gt; 编译选择的文件、包或模块&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Option + R&lt;/code&gt; 弹出 Run 的可选择菜单&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Option + D&lt;/code&gt; 弹出 Debug 的可选择菜单&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + R&lt;/code&gt; 运行&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + D&lt;/code&gt; 调试&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + R, Control + Shift + D&lt;/code&gt; 从编辑器运行上下文环境配置&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Debugging（调试）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;F8&lt;/code&gt; 进入下一步，如果当前行断点是一个方法，则不进入当前方法体内&lt;/li&gt; &lt;li&gt;&lt;code&gt;F7&lt;/code&gt; 进入下一步，如果当前行断点是一个方法，则进入当前方法体内，如果该方法体还有方法，则不会进入该内嵌的方法中&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + F7&lt;/code&gt; 智能步入，断点所在行上有多个方法调用，会弹出进入哪个方法&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + F8&lt;/code&gt; 跳出&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + F9&lt;/code&gt; 运行到光标处，如果光标前有其他断点会进入到该断点&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + F8&lt;/code&gt; 计算表达式（可以更改变量值使其生效）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + R&lt;/code&gt; 恢复程序运行，如果该断点下面代码还有断点则停在下一个断点上&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F8&lt;/code&gt; 切换断点（若光标当前行有断点则取消断点，没有则加上断点）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F8&lt;/code&gt; 查看断点信息&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Navigation（导航）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + O&lt;/code&gt; 查找类文件&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + O&lt;/code&gt; 查找所有类型文件、打开文件、打开目录，打开目录需要在输入的内容前面或后面加一个反斜杠&lt;code&gt;/&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + O&lt;/code&gt; 前往指定的变量 / 方法&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + 方向键左 / Control + 方向键右&lt;/code&gt; 左右切换打开的编辑tab页&lt;/li&gt; &lt;li&gt;&lt;code&gt;F12&lt;/code&gt; 返回到前一个工具窗口&lt;/li&gt; &lt;li&gt;&lt;code&gt;Esc&lt;/code&gt; 从工具窗口进入代码文件窗口&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + Esc&lt;/code&gt; 隐藏当前或最后一个活动的窗口，且光标进入代码文件窗口&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F4&lt;/code&gt; 关闭活动run/messages/find/... tab&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + L&lt;/code&gt; 在当前文件跳转到某一行的指定处&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + E&lt;/code&gt; 显示最近打开的 &lt;strong&gt;文件记录&lt;/strong&gt; 列表&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + E&lt;/code&gt; 显示最近打开的 &lt;strong&gt;文件代码位置记录&lt;/strong&gt; 列表&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + 方向键左 / Option + 方向键右&lt;/code&gt; 光标跳转到当前单词 / 中文句的左 / 右侧开头位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + 方向键左 / Command + Option + 方向键右&lt;/code&gt; 退回 / 前进到上一个操作的地方&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + Delete&lt;/code&gt; 跳转到最后一个编辑的地方&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + F1&lt;/code&gt; 显示当前文件选择目标弹出层，弹出层中有很多目标可以进行选择(如在代码编辑窗口可以选择显示该文件的Finder)&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + B / Command + 鼠标点击&lt;/code&gt; 进入光标所在的方法/变量的接口或是定义处&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + B&lt;/code&gt; 跳转到实现处，在某个调用的方法名上使用会跳到具体的实现处，可以跳过接口&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Space, Command + Y&lt;/code&gt; 快速打开光标所在方法、类的定义&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + B&lt;/code&gt; 跳转到类型声明处&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + U&lt;/code&gt; 前往当前光标所在方法的父类的方法 / 接口定义&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + 方向键下 / Control + 方向键上&lt;/code&gt; 当前光标跳转到当前文件的前一个/后一个方法名位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + ] / Command + [&lt;/code&gt; 移动光标到当前所在代码的花括号开始/结束位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F12&lt;/code&gt; 弹出当前文件结构层，可以在弹出的层上直接输入进行筛选（可用于搜索类中的方法）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + H&lt;/code&gt; 显示当前类的层次结构&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + H&lt;/code&gt; 显示方法层次结构&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Option + H&lt;/code&gt; 显示调用层次结构&lt;/li&gt; &lt;li&gt;&lt;code&gt;F2 / Shift + F2&lt;/code&gt; 跳转到下一个/上一个突出错误或警告的位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;F4 / Command + 方向键下&lt;/code&gt; 编辑/查看代码源&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Home&lt;/code&gt; 显示到当前文件的导航条&lt;/li&gt; &lt;li&gt;&lt;code&gt;F3&lt;/code&gt;选中文件/文件夹/代码行，添加/取消书签&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + F3&lt;/code&gt; 选中文件/文件夹/代码行，使用助记符添加/取消书签&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + 0...Control + 9&lt;/code&gt; 定位到对应数值的书签位置&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F3&lt;/code&gt; 显示所有书签&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Refactoring（重构）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;F5&lt;/code&gt; 复制文件到指定目录&lt;/li&gt; &lt;li&gt;&lt;code&gt;F6&lt;/code&gt; 移动文件到指定目录&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Delete&lt;/code&gt; 在文件上为安全删除文件，弹出确认框&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + F6&lt;/code&gt; 重命名文件&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + F6&lt;/code&gt; 更改签名&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + N&lt;/code&gt; 一致性&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + M&lt;/code&gt; 将选中的代码提取为方法&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + V&lt;/code&gt; 提取变量&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + F&lt;/code&gt; 提取字段&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + C&lt;/code&gt; 提取常量&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + P&lt;/code&gt; 提取参数&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;VCS/Local History（版本控制/本地历史记录）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + K&lt;/code&gt; 提交代码到版本控制器&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + T&lt;/code&gt; 从版本控制器更新代码&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Shift + C&lt;/code&gt; 查看最近的变更记录&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + C&lt;/code&gt; 快速弹出版本控制器操作面板&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Live Templates（动态代码模板）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + Option + J&lt;/code&gt; 弹出模板选择窗口，将选定的代码使用动态模板包住&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + J&lt;/code&gt; 插入自定义动态代码模板&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;General（通用）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + 1...Command + 9&lt;/code&gt; 打开相应编号的工具窗口&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + S&lt;/code&gt; 保存所有&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Option + Y&lt;/code&gt; 同步、刷新&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Command + F&lt;/code&gt; 切换全屏模式&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + Shift + F12&lt;/code&gt; 切换最大化编辑器&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Shift + F&lt;/code&gt; 添加到收藏夹&lt;/li&gt; &lt;li&gt;&lt;code&gt;Option + Shift + I&lt;/code&gt; 检查当前文件与当前的配置文件&lt;/li&gt; &lt;li&gt;Control + ` 快速切换当前的scheme（切换主题、代码样式等）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + ,&lt;/code&gt; 打开IDEA系统设置&lt;/li&gt; &lt;li&gt;&lt;code&gt;Command + ;&lt;/code&gt; 打开项目结构对话框&lt;/li&gt; &lt;li&gt;&lt;code&gt;Shift + Command + A&lt;/code&gt; 查找动作（可设置相关选项）&lt;/li&gt; &lt;li&gt;&lt;code&gt;Control + Shift + Tab&lt;/code&gt; 编辑窗口标签和工具窗口之间切换（如果在切换的过程加按上delete，则是关闭对应选中的窗口）&lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Other（一些官方文档上没有体现的快捷键）&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;Command + Shift +8&lt;/code&gt; 竖编辑模式&lt;/li&gt; &lt;/ul&gt; &lt;h1&gt;从 Windows 过度到 Mac 必备快捷键对照表&lt;/h1&gt; &lt;h2&gt;Mac 键盘符号&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;图标&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;⌘&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇧&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇪&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Caps Lock&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; = &lt;kbd&gt;Alt&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌃&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↩&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌫&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌦&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↑&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;上箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↓&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;下箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;←&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;左箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;→&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;右箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇞&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; = &lt;kbd&gt;Page Up&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇟&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; = &lt;kbd&gt;Page Down&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Home&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;←&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;End&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;→&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt; = &lt;kbd&gt;右制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇤&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Tab&lt;/kbd&gt; = &lt;kbd&gt;左制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⎋&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Esc&lt;/kbd&gt; = &lt;kbd&gt;Escape&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⏏&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;电源开关键&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;`&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示版本控制常用操作菜单弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示当前文件选择目标弹出层，弹出层中有很多目标可以进行选择&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查询所选对象/变量被引用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;IntelliJ IDEA 根据光标所在问题，提供快速修复选择，光标放在的位置不同提示的结果也不同&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Insert&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码自动生成，如生成对象的 set / get 方法，构造函数，toString() 等&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的前一个方法名位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的后一个方法名位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示对应数值的选项卡，其中 1 是 Project 用得最多&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本查找&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本替换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;撤销&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;G&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到指定行数位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Y&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标所在行 或 删除选中的行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;D&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;D&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制光标所在行 或 复制选择内容，并把复制内容插入光标位置下面&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;方向键上&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展选中范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示最近打开的文件记录列表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入的 &lt;strong&gt;类名&lt;/strong&gt; 查找类文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;插入自定义动态代码模板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;P&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;P&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;方法参数提示显示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Q&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前位置变量、方法的 Documentation 内容显示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前往当前光标所在的方法的父类的方法 / 接口定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;进入光标所在的方法/变量的接口或是定义处，等效于 &lt;code&gt;Ctrl + 左键单击&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;注释光标所在行代码，会根据当前不同文件类型使用不同的注释符号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在光标所在的错误代码处显示错误信息&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中文件 / 文件夹，使用助记符设定 / 取消书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出当前文件结构层，可以在弹出的层上直接输入，进行筛选&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;基础代码补全，默认在 Windows 系统上被输入法占用，需要进行修改，建议修改为 &lt;code&gt;Ctrl + 逗号&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Fn&lt;/kbd&gt;+ &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标后面的单词或是中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;BackSpace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标前面的单词或是中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;定位到对应数值的书签位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件标题上，弹出该文件路径&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的左侧开头位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的右侧开头位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;预设中没有该快捷键&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向前效果&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;预设中没有该快捷键&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向后效果&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command + F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出书签显示层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消缩进&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;开始新一行。光标所在行下空出一行，光标定位到新行位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + 左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件名上按此快捷键，可以关闭当前打开文件&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择 / 添加 task&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键双击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键双击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择被双击的单词 / 中文句，按住不放，可以同时选择其他单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向上移动&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向下移动&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;格式化代码，可以对当前文件和整个包目录使用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;优化导入的类，可以对当前文件和整个包目录使用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码弹出环绕选项弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;逗号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开 IntelliJ IDEA 系统设置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标所在行上空出一行，光标定位到新行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上一个操作的地方&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前进到上一个操作的地方&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容查找整个项目 或 指定目录内文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容替换对应内容，范围为整个项目 或 指定目录内文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动将下一行合并到当前行末尾&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消撤销&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;方向键下&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式取消选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展取消选中范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;通过文件名定位 / 打开文件 / 目录，打开目录需要在输入的内容后面多加一个正斜杠&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码进行大 / 小写轮流转换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对当前类生成单元测试类，如果已经存在的单元测试类则可以进行选择&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制当前文件磁盘路径到剪贴板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到类型声明处&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码块注释&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的顶部中括号位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的底部中括号位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开所有代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠所有代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;高亮显示所有该选中文本，按Esc高亮消失&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑器最大化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动结束代码，行末自动添加分号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Backspace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Backspace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上次修改的地方&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;快速添加指定数值的书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;把光标放在某个类变量上，按此快捷键可以直接定位到该类中&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的左侧开头位置，同时选中该单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的右侧开头位置，同时选中该单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到上一个方法前面，调整方法排序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到下一个方法前面，调整方法排序&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;无格式黏贴&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;;&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开当前项目设置&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Other&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到下一个高亮错误 或 警告位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑源&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;添加书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;回到前一个工具窗口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;缩进&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;从工具窗口进入代码文件窗口&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h1&gt;最特殊的快捷键 Alt + Enter 介绍&lt;/h1&gt; &lt;h2&gt;说明&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;这是一个非常特殊的快捷键，有必要拿出来单独讲。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;强烈注意&lt;/strong&gt;：此快捷键跟光标所在位置有着很严重关联关系，光标放的位置不同，使用此快捷键出来的菜单选项完全不一样。&lt;/li&gt; &lt;li&gt;可以从几个思路：Java 类、JSP、HTML、JavaScript、CSS、SQL 等文件类型&lt;/li&gt; &lt;li&gt;下面演示的各个功能是基于：IntelliJ IDEA 2016.1.1，如果你使用早期版本，可能不一定有对应的功能。&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;智能辅助&lt;/h2&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;在 &lt;strong&gt;接口类&lt;/strong&gt; 中，如果光标当前所在的方法，已经在 &lt;strong&gt;接口实现类&lt;/strong&gt; 中生成了，则此快捷键的效果是跳转。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;在 &lt;strong&gt;接口类&lt;/strong&gt; 中添加一个方法后，让该 &lt;strong&gt;接口实现类&lt;/strong&gt; 也跟着生成&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;在 &lt;strong&gt;接口实现类&lt;/strong&gt; 中添加一个方法后，让该 &lt;strong&gt;接口类&lt;/strong&gt; 也跟着生成&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;对当前光标所在类，生成单元测试类&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;对当前光标所在类，创建子类，常用在对接口生成接口实现类&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;移除未使用的变量、对象等元素&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;对属性创建 set、get 方法&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;添加 doc，只能把光标放在方法名或是变量名等这类元素上才会有&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;把自己造的单词加入词库中，让拼写单词检查错误的波浪线效果消失。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;自己造的词库在上图所示位置&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;快速移除当前类所继承的接口，并且同时清空已经写好的该接口所有的 Override 方法。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;光标只能方式 &lt;strong&gt;接口实现类&lt;/strong&gt; 上的 &lt;strong&gt;接口对象单词&lt;/strong&gt; 上才可以实现。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;修改光标当前元素的作用域&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;给调用的方法生成返回值&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;根据返回值自动强转&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;对光标所在的对象进行包导入&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;切换成静态导入&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;根据 Language Level 级别不同，JDK 特性不同，给不同意见。Language Level 的含义在其他章节有讲过。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;给 Hibernate 的 Entity 对象分配数据源，从而产生一系列智能功能&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt;</content:encoded>
      <pubDate>Tue, 14 Jul 2020 12:31:51 GMT</pubDate>
    </item>
    <item>
      <title>编码5分钟，命名2小时？Java开发都需要参考的一份命名规范！</title>
      <link>https://maruifu.cn/article/150</link>
      <content:encoded>&lt;p&gt;简洁清爽的代码风格应该是大多数工程师所期待的。在工作中笔者常常因为起名字而纠结，夸张点可以说是编程5分钟，命名两小时！&lt;/p&gt; &lt;p&gt;每个公司都有不同的标准，目的是为了保持统一，减少沟通成本，提升团队研发效能。所以本文中是笔者结合阿里巴巴开发规范，以及工作中的见闻针对Java领域相关命名进行整理和总结，仅供参考。&lt;/p&gt; &lt;h2&gt;一，Java中的命名规范&lt;/h2&gt; &lt;p&gt;好的命名能体现出代码的特征，含义或者是用途，让阅读者可以根据名称的含义快速厘清程序的脉络。不同语言中采用的命名形式大相径庭，Java中常用到的命名形式共有三种，既首字母大写的UpperCamelCase，首字母小写的lowerCamelCase以及全部大写的并用下划线分割单词的UPPERCAMELUNSER_SCORE。通常约定，&lt;strong&gt;类一般采用大驼峰命名，方法和局部变量使用小驼峰命名，而大写下划线命名通常是常量和枚举中使用。&lt;/strong&gt;&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;类型(名)&lt;/th&gt;&lt;th&gt;约束&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;项目&lt;/td&gt;&lt;td&gt;全部小写多个单词用中划线分隔‘-’&lt;/td&gt;&lt;td&gt;spring-cloud&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;包&lt;/td&gt;&lt;td&gt;全部小写&lt;/td&gt;&lt;td&gt;com.alibaba.fastjson&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;类&lt;/td&gt;&lt;td&gt;单词首字母大写&lt;/td&gt;&lt;td&gt;Feature,FieldDeserializer&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;变量&lt;/td&gt;&lt;td&gt;首字母小写多个单词组成时，除首个单词其他单词首字母都要大写&lt;/td&gt;&lt;td&gt;password, userName&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;常量&lt;/td&gt;&lt;td&gt;全部大写，多个单词，用'_'分隔&lt;/td&gt;&lt;td&gt;CACHEEXPIREDTIME&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;方法&lt;/td&gt;&lt;td&gt;同变量&lt;/td&gt;&lt;td&gt;read(), getById(Long id)&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;二，包命名&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;包名&lt;/strong&gt;统一使用&lt;strong&gt;小写&lt;/strong&gt;，&lt;strong&gt;点分隔符&lt;/strong&gt;之间有且仅有一个自然语义的英文单词或者多个单词自然连接到一块（如 springframework，deepspace不需要使用任何分割）。包名统一使用单数形式，如果类命有复数含义，则可以使用复数形式。&lt;/p&gt; &lt;p&gt;包名的构成可以分为以下几四部分【前缀】 【发起者名】【项目名】【模块名】。常见的前缀可以分为以下几种：&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;前缀&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;indi或onem&lt;/td&gt;&lt;td&gt;indi.发起者名.项目名.模块名.……&lt;/td&gt;&lt;td&gt;个体项目个人发起，但非自己独自完成可公开或私有项目，copyright主要属于发起者。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pers&lt;/td&gt;&lt;td&gt;pers.个人名.项目名.模块名.……&lt;/td&gt;&lt;td&gt;个人项目指个人发起，独自完成，可分享的项目copyright主要属于个人&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;priv&lt;/td&gt;&lt;td&gt;priv.个人名.项目名.模块名.……&lt;/td&gt;&lt;td&gt;私有项目，指个人发起，独自完成非公开的私人使用的项目，copyright属于个人。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;team&lt;/td&gt;&lt;td&gt;team.团队名.项目名.模块名.……&lt;/td&gt;&lt;td&gt;团队项目，指由团队发起并由该团队开发的项目copyright属于该团队所有&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;顶级域名&lt;/td&gt;&lt;td&gt;com.公司名.项目名.模块名.……&lt;/td&gt;&lt;td&gt;公司项目copyright由项目发起的公司所有&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;三，类命名&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;类名使用大驼峰命名形式&lt;/strong&gt;，类命通常时&lt;strong&gt;名词或名词短语&lt;/strong&gt;，接口名除了用名词和名词短语以外，还可以使用形容词或形容词短语，如Cloneable，Callable等，表示实现该接口的类有某种功能或能力。对于测试类则以它要测试的类开头，以Test结尾，如HashMapTest。&lt;/p&gt; &lt;p&gt;对于一些特殊特有名词缩写也可以使用全大写命名，比如XMLHttpRequest，不过笔者认为缩写三个字母以内都大写，超过三个字母则按照要给单词算。这个没有标准如阿里巴巴中fastjson用JSONObject作为类命，而google则使用JsonObjectRequest命名，对于这种特殊的缩写，原则是统一就好。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;属性(类)&lt;/th&gt;&lt;th&gt;约束&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;抽象&lt;/td&gt;&lt;td&gt;Abstract 或Base 开头&lt;/td&gt;&lt;td&gt;BaseUserService&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;枚举&lt;/td&gt;&lt;td&gt;Enum 作为后缀&lt;/td&gt;&lt;td&gt;OSType&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;工具&lt;/td&gt;&lt;td&gt;Utils作为后缀&lt;/td&gt;&lt;td&gt;StringUtils&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;异常&lt;/td&gt;&lt;td&gt;Exception结尾&lt;/td&gt;&lt;td&gt;RuntimeException&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;接口实现&lt;/td&gt;&lt;td&gt;接口名+ Impl&lt;/td&gt;&lt;td&gt;UserServiceImpl&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;领域模型相&lt;/td&gt;&lt;td&gt;/DO/DTO/VO/DAO&lt;/td&gt;&lt;td&gt;正例：UserDAO反例：UserDao&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;设计模式相关&lt;/td&gt;&lt;td&gt;Builder，Factory等&lt;/td&gt;&lt;td&gt;当使用到设计模式时要使用对应的设计模式作为后缀如ThreadFactory&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;处理特定功能&lt;/td&gt;&lt;td&gt;Handler，PredicateValidator&lt;/td&gt;&lt;td&gt;表示处理器，校验器，断言这些类工厂还有配套的方法名如handle，predicate，validate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;测试&lt;/td&gt;&lt;td&gt;Test后缀&lt;/td&gt;&lt;td&gt;UserServiceTest表示用来测试UserService类的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;MVC分层&lt;/td&gt;&lt;td&gt;Controller，ServiceServiceImpl，DAO后缀&lt;/td&gt;&lt;td&gt;UserManageControllerUserManageDAO&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;四，方法&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;方法命名采用小驼峰的形式&lt;/strong&gt;，首字小写，往后的每个单词首字母都要大写。和类名不同的是，方法命名一般为&lt;strong&gt;动词或动词短语&lt;/strong&gt;，与参数或参数名共同组成动宾短语，即动词 + 名词。一个好的函数名一般能通过名字直接获知该函数实现什么样的功能。&lt;/p&gt; &lt;h3&gt;4.1 返回真伪值的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;is&lt;/td&gt;&lt;td&gt;对象是否符合期待的状态&lt;/td&gt;&lt;td&gt;isValid&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;can&lt;/td&gt;&lt;td&gt;对象能否执行所期待的动作&lt;/td&gt;&lt;td&gt;canRemove&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;should&lt;/td&gt;&lt;td&gt;调用方执行某个命令或方法是好还是不好应不应该，或者说推荐还是不推荐&lt;/td&gt;&lt;td&gt;shouldMigrate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;has&lt;/td&gt;&lt;td&gt;对象是否持有所期待的数据和属性&lt;/td&gt;&lt;td&gt;hasObservers&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;needs&lt;/td&gt;&lt;td&gt;调用方是否需要执行某个命令或方法&lt;/td&gt;&lt;td&gt;needsMigrate&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.2 用来检查的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;ensure&lt;/td&gt;&lt;td&gt;检查是否为期待的状态&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;不是则抛出异常或返回error code&lt;/td&gt;&lt;td&gt;ensureCapacity&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;validate&lt;/td&gt;&lt;td&gt;检查是否为正确的状态&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;不是则抛出异常或返回error code&lt;/td&gt;&lt;td&gt;validateInputs&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.3 按需求才执行的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;IfNeeded&lt;/td&gt;&lt;td&gt;需要的时候执行不需要则什么都不做&lt;/td&gt;&lt;td&gt;drawIfNeeded&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;might&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;mightCreate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;try&lt;/td&gt;&lt;td&gt;尝试执行失败时抛出异常或是返回errorcode&lt;/td&gt;&lt;td&gt;tryCreate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;OrDefault&lt;/td&gt;&lt;td&gt;尝试执行失败时返回默认值&lt;/td&gt;&lt;td&gt;getOrDefault&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;OrElse&lt;/td&gt;&lt;td&gt;尝试执行失败时返回实际参数中指定的值&lt;/td&gt;&lt;td&gt;getOrElse&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;force&lt;/td&gt;&lt;td&gt;强制尝试执行error抛出异常或是返回值&lt;/td&gt;&lt;td&gt;forceCreate, forceStop&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.4 异步相关方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;blocking&lt;/td&gt;&lt;td&gt;线程阻塞方法&lt;/td&gt;&lt;td&gt;blockingGetUser&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;InBackground&lt;/td&gt;&lt;td&gt;执行在后台线程&lt;/td&gt;&lt;td&gt;doInBackground&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;Async&lt;/td&gt;&lt;td&gt;异步方法&lt;/td&gt;&lt;td&gt;sendAsync&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;suf&lt;/td&gt;&lt;td&gt;Sync&lt;/td&gt;&lt;td&gt;同步方法&lt;/td&gt;&lt;td&gt;sendSync&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;schedule&lt;/td&gt;&lt;td&gt;Job和Task放入队列&lt;/td&gt;&lt;td&gt;schedule, scheduleJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;post&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;postJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;execute&lt;/td&gt;&lt;td&gt;执行异步或同步方法&lt;/td&gt;&lt;td&gt;execute,executeTask&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;start&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;star,tstartJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;cancel&lt;/td&gt;&lt;td&gt;停止异步方法&lt;/td&gt;&lt;td&gt;cance,cancelJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre/alo&lt;/td&gt;&lt;td&gt;stop&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;stop,stopJob&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.5 回调方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;on&lt;/td&gt;&lt;td&gt;事件发生时执行&lt;/td&gt;&lt;td&gt;onCompleted&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;before&lt;/td&gt;&lt;td&gt;事件发生前执行&lt;/td&gt;&lt;td&gt;beforeUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;preUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;will&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;willUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;after&lt;/td&gt;&lt;td&gt;事件发生后执行&lt;/td&gt;&lt;td&gt;afterUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;post&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;postUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;did&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;didUpdate&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pre&lt;/td&gt;&lt;td&gt;should&lt;/td&gt;&lt;td&gt;确认事件是否可以执行&lt;/td&gt;&lt;td&gt;shouldUpdate&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.6 操作对象生命周期的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;initialize&lt;/td&gt;&lt;td&gt;初始化或延迟初始化使用&lt;/td&gt;&lt;td&gt;initialize&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pause&lt;/td&gt;&lt;td&gt;暂停&lt;/td&gt;&lt;td&gt;onPause , pause&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;stop&lt;/td&gt;&lt;td&gt;停止&lt;/td&gt;&lt;td&gt;onStop, stop&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;abandon&lt;/td&gt;&lt;td&gt;销毁的替代&lt;/td&gt;&lt;td&gt;abandon&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;destroy&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;destroy&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;dispose&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;td&gt;dispose&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.7 与集合操作相关的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;contains&lt;/td&gt;&lt;td&gt;是包含指定对象相同的对象&lt;/td&gt;&lt;td&gt;contains&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;add&lt;/td&gt;&lt;td&gt;添加&lt;/td&gt;&lt;td&gt;addJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;append&lt;/td&gt;&lt;td&gt;添加&lt;/td&gt;&lt;td&gt;appendJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;insert&lt;/td&gt;&lt;td&gt;插入到下标n&lt;/td&gt;&lt;td&gt;insertJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;put&lt;/td&gt;&lt;td&gt;添加与key对应的元素&lt;/td&gt;&lt;td&gt;putJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;remove&lt;/td&gt;&lt;td&gt;移除元素&lt;/td&gt;&lt;td&gt;removeJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;enqueue&lt;/td&gt;&lt;td&gt;添加到队列的最末位&lt;/td&gt;&lt;td&gt;enqueueJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;dequeue&lt;/td&gt;&lt;td&gt;从队列中头部取出并移除&lt;/td&gt;&lt;td&gt;dequeueJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;push&lt;/td&gt;&lt;td&gt;添加到栈头&lt;/td&gt;&lt;td&gt;pushJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pop&lt;/td&gt;&lt;td&gt;从栈头取出并移除&lt;/td&gt;&lt;td&gt;popJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;peek&lt;/td&gt;&lt;td&gt;从栈头取出但不移除&lt;/td&gt;&lt;td&gt;peekJob&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;find&lt;/td&gt;&lt;td&gt;寻找符合条件的某物&lt;/td&gt;&lt;td&gt;findById&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.8 与数据相关的方法&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;th&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;create&lt;/td&gt;&lt;td&gt;新创建&lt;/td&gt;&lt;td&gt;createAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;new&lt;/td&gt;&lt;td&gt;新创建&lt;/td&gt;&lt;td&gt;newAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;from&lt;/td&gt;&lt;td&gt;从既有的某物新建或是从其他的数据新建&lt;/td&gt;&lt;td&gt;fromConfig&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;to&lt;/td&gt;&lt;td&gt;转换&lt;/td&gt;&lt;td&gt;toString&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;update&lt;/td&gt;&lt;td&gt;更新既有某物&lt;/td&gt;&lt;td&gt;updateAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;load&lt;/td&gt;&lt;td&gt;读取&lt;/td&gt;&lt;td&gt;loadAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;fetch&lt;/td&gt;&lt;td&gt;远程读取&lt;/td&gt;&lt;td&gt;fetchAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;delete&lt;/td&gt;&lt;td&gt;删除&lt;/td&gt;&lt;td&gt;deleteAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;remove&lt;/td&gt;&lt;td&gt;删除&lt;/td&gt;&lt;td&gt;removeAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;save&lt;/td&gt;&lt;td&gt;保存&lt;/td&gt;&lt;td&gt;saveAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;store&lt;/td&gt;&lt;td&gt;保存&lt;/td&gt;&lt;td&gt;storeAccount&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;commit&lt;/td&gt;&lt;td&gt;保存&lt;/td&gt;&lt;td&gt;commitChange&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;apply&lt;/td&gt;&lt;td&gt;保存或应用&lt;/td&gt;&lt;td&gt;applyChange&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;clear&lt;/td&gt;&lt;td&gt;清除或是恢复到初始状态&lt;/td&gt;&lt;td&gt;clearAll&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;reset&lt;/td&gt;&lt;td&gt;清除或是恢复到初始状态&lt;/td&gt;&lt;td&gt;resetAll&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;4.9 成对出现的动词&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;单词&lt;/th&gt;&lt;th&gt;意义&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;get获取&lt;/td&gt;&lt;td&gt;set 设置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;add 增加&lt;/td&gt;&lt;td&gt;remove 删除&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;create 创建&lt;/td&gt;&lt;td&gt;destory 移除&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;start 启动&lt;/td&gt;&lt;td&gt;stop 停止&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;open 打开&lt;/td&gt;&lt;td&gt;close 关闭&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;read 读取&lt;/td&gt;&lt;td&gt;write 写入&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;load 载入&lt;/td&gt;&lt;td&gt;save 保存&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;create 创建&lt;/td&gt;&lt;td&gt;destroy 销毁&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;begin 开始&lt;/td&gt;&lt;td&gt;end 结束&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;backup 备份&lt;/td&gt;&lt;td&gt;restore 恢复&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;import 导入&lt;/td&gt;&lt;td&gt;export 导出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;split 分割&lt;/td&gt;&lt;td&gt;merge 合并&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;inject 注入&lt;/td&gt;&lt;td&gt;extract 提取&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;attach 附着&lt;/td&gt;&lt;td&gt;detach 脱离&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;bind 绑定&lt;/td&gt;&lt;td&gt;separate 分离&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;view 查看&lt;/td&gt;&lt;td&gt;browse 浏览&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;edit 编辑&lt;/td&gt;&lt;td&gt;modify 修改&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;select 选取&lt;/td&gt;&lt;td&gt;mark 标记&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;copy 复制&lt;/td&gt;&lt;td&gt;paste 粘贴&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;undo 撤销&lt;/td&gt;&lt;td&gt;redo 重做&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;insert 插入&lt;/td&gt;&lt;td&gt;delete 移除&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;add 加入&lt;/td&gt;&lt;td&gt;append 添加&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;clean 清理&lt;/td&gt;&lt;td&gt;clear 清除&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;index 索引&lt;/td&gt;&lt;td&gt;sort 排序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;find 查找&lt;/td&gt;&lt;td&gt;search 搜索&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;increase 增加&lt;/td&gt;&lt;td&gt;decrease 减少&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;play 播放&lt;/td&gt;&lt;td&gt;pause 暂停&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;launch 启动&lt;/td&gt;&lt;td&gt;run 运行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;compile 编译&lt;/td&gt;&lt;td&gt;execute 执行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;debug 调试&lt;/td&gt;&lt;td&gt;trace 跟踪&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;observe 观察&lt;/td&gt;&lt;td&gt;listen 监听&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;build 构建&lt;/td&gt;&lt;td&gt;publish 发布&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;input 输入&lt;/td&gt;&lt;td&gt;output 输出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;encode 编码&lt;/td&gt;&lt;td&gt;decode 解码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;encrypt 加密&lt;/td&gt;&lt;td&gt;decrypt 解密&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;compress 压缩&lt;/td&gt;&lt;td&gt;decompress 解压缩&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;pack 打包&lt;/td&gt;&lt;td&gt;unpack 解包&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;parse 解析&lt;/td&gt;&lt;td&gt;emit 生成&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;connect 连接&lt;/td&gt;&lt;td&gt;disconnect 断开&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;send 发送&lt;/td&gt;&lt;td&gt;receive 接收&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;download 下载&lt;/td&gt;&lt;td&gt;upload 上传&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;refresh 刷新&lt;/td&gt;&lt;td&gt;synchronize 同步&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;update 更新&lt;/td&gt;&lt;td&gt;revert 复原&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;lock 锁定&lt;/td&gt;&lt;td&gt;unlock 解锁&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;check out 签出&lt;/td&gt;&lt;td&gt;check in 签入&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;submit 提交&lt;/td&gt;&lt;td&gt;commit 交付&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;push 推&lt;/td&gt;&lt;td&gt;pull 拉&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;expand 展开&lt;/td&gt;&lt;td&gt;collapse 折叠&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;begin 起始&lt;/td&gt;&lt;td&gt;end 结束&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;start 开始&lt;/td&gt;&lt;td&gt;finish 完成&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;enter 进入&lt;/td&gt;&lt;td&gt;exit 退出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;abort 放弃&lt;/td&gt;&lt;td&gt;quit 离开&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;obsolete 废弃&lt;/td&gt;&lt;td&gt;depreciate 废旧&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;collect 收集&lt;/td&gt;&lt;td&gt;aggregate 聚集&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;五，变量&amp;amp;常量命名&lt;/h2&gt; &lt;h3&gt;5.1 变量命名&lt;/h3&gt; &lt;p&gt;变量是指在程序运行中可以改变其值的量，包括成员变量和局部变量。变量名由多单词组成时，第一个单词的首字母小写，其后单词的首字母大写，俗称骆驼式命名法（也称驼峰命名法），如 computedValues，index、变量命名时，尽量简短且能清楚的表达变量的作用，命名体现具体的业务含义即可。&lt;/p&gt; &lt;p&gt;变量名不应以下划线或美元符号开头，尽管这在语法上是允许的。变量名应简短且富于描述。变量名的选用应该易于记忆，即，能够指出其用途。尽量避免单个字符的变量名，除非是一次性的临时变量。pojo中的布尔变量，都不要加is(数据库中的布尔字段全都要加 is_ 前缀)。&lt;/p&gt; &lt;h3&gt;5.2 常量命名&lt;/h3&gt; &lt;p&gt;常量命名CONSTANT_CASE，一般采用全部大写（作为方法参数时除外），单词间用下划线分割。那么什么是常量呢？&lt;/p&gt; &lt;p&gt;常量是在作用域内保持不变的值，一般使用final进行修饰。一般分为三种，全局常量（public static final修饰），类内常量（private static final 修饰）以及局部常量（方法内，或者参数中的常量），局部常量比较特殊，通常采用小驼峰命名即可。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**  * 一个demo  *  * @author XiaoMage  * @date 2020-07-07 00:25  **/  public class HelloWorld {       //局部常量(正例)     public  static  final long  USER_MESSAGE_CACHE_EXPIRE_TIME  =  3600;          //局部常量(反例，命名不清晰）     public static final long  MESSAGE_CACHE_TIME = 3600;      // 全局常量     private  static final String ERROR_MESSAGE  = &amp;quot;error message&amp;quot;;          //成员变量     private int  currentUserId;          /**      * 控制台打印 {@code message} 信息      *      * @param message 消息体，局部常量      */     public void  sayHello ( final String  message ) {           System.out.println(&amp;quot;Hello world!&amp;quot;);       }   } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;常量一般都有自己的业务含义,&lt;strong&gt;不要害怕长度过长而进行省略或者缩写&lt;/strong&gt;。如，用户消息缓存过期时间的表示，那种方式更佳清晰，交给你来评判。&lt;/p&gt; &lt;h2&gt;通用命名规则&lt;/h2&gt; &lt;p&gt;1.尽量不要使用拼音；&lt;strong&gt;杜绝拼音和英文混用&lt;/strong&gt;。对于一些通用的表示或者难以用英文描述的可以采用拼音，一旦采用拼音就坚决不能和英文混用。正例：BeiJing， HangZhou 反例：validateCanShu&lt;/p&gt; &lt;p&gt;2.命名过程中尽量不要出现特殊的字符，常量除外。&lt;/p&gt; &lt;p&gt;3.尽量不要和jdk或者框架中已存在的类重名，也不能使用java中的关键字命名。&lt;/p&gt; &lt;p&gt;4.&amp;gt;妙用介词，如for(可以用同音的4代替), to(可用同音的2代替), from, with，of等。如类名采用User4RedisDO，方法名getUserInfoFromRedis，convertJson2Map等。&lt;/p&gt; &lt;h2&gt;六，代码注解&lt;/h2&gt; &lt;h3&gt;6.1 注解的原则&lt;/h3&gt; &lt;p&gt;好的命名增加代码阅读性，代码的命名往往有严格的限制。而注解不同，程序员往往可以自由发挥，单并不意味着可以为所欲为之胡作非为。优雅的注解通常要满足三要素。&lt;/p&gt; &lt;p&gt;1.Nothing is strange 没有注解的代码对于阅读者非常不友好，哪怕代码写的在清除，阅读者至少从心理上会有抵触，更何况代码中往往有许多复杂的逻辑，所以一定要写注解，不仅要记录代码的逻辑，还有说清楚修改的逻辑。&lt;/span&gt;&lt;/p&gt; &lt;p&gt;2.Less is more 从代码维护角度来讲，代码中的注解一定是精华中的精华。合理清晰的命名能让代码易于理解，对于逻辑简单且命名规范，能够清楚表达代码功能的代码不需要注解。滥用注解会增加额外的负担，更何况大部分都是废话。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;// 根据id获取信息【废话注解】 &lt;/code&gt;&lt;/pre&gt; &lt;ul&gt; &lt;li&gt;Advance with the time 注解应该随着代码的变动而改变，注解表达的信息要与代码中完全一致。通常情况下修改代码后一定要修改注解。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;6.2 注解格式&lt;/h3&gt; &lt;p&gt;注解大体上可以分为两种，一种是javadoc注解，另一种是简单注解。javadoc注解可以生成JavaAPI为外部用户提供有效的支持javadoc注解通常在使用IDEA，或者Eclipse等开发工具时都可以自动生成，也支持自定义的注解模板，仅需要对对应的字段进行解释。参与同一项目开发的同学，尽量设置成相同的注解模板。&lt;/p&gt; &lt;h4&gt;a. 包注解&lt;/h4&gt; &lt;p&gt;包注解在工作中往往比较特殊，通过包注解可以快速知悉当前包下代码是用来实现哪些功能，强烈建议工作中加上，尤其是对于一些比较复杂的包，包注解一般在包的根目录下，名称统一为package-info.java。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**  * 落地也质量检测  * 1. 用来解决什么问题  * 对广告主投放的广告落地页进行性能检测，模拟不同的系统，如Android，IOS等; 模拟不同的网络：2G，3G，4G，wifi等  * 2. 如何实现  * 基于chrome浏览器，用chromedriver驱动浏览器，设置对应的网络，OS参数，获取到浏览器返回结果。  * 注意：网络环境配置信息{@link cn.mycookies.landingpagecheck.meta.NetWorkSpeedEnum}目前使用是常规速度，可以根据实际情况进行调整  * @author xiaomage  * @time 2020/07/7 20:3 下午  */  package cn.maruifu.landingpagecheck; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;b. 类注接&lt;/h4&gt; &lt;p&gt;javadoc注解中，每个类都必须有注解。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**  * Copyright (C), 2019-2020, XiaoMage  balabala...  * 类的介绍：这是一个用来做什么事情的类，有哪些功能，用到的技术.....  * @author   类创建者姓名 保持对齐  * @date     创建日期 保持对齐  * @version  版本号 保持对齐  */ &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;c. 属性注解&lt;/h4&gt; &lt;p&gt;在每个属性前面必须加上属性注释，通常有一下两种形式，至于怎么选择，你高兴就好，不过一个项目中要保持统一。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/** 提示信息 */ private String userName; /**  * 密码  */ private String password; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;d. 方法注释&lt;/h4&gt; &lt;p&gt;在每个方法前面必须加上方法注释，对于方法中的每个参数，以及返回值都要有说明。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**   * 方法的详细说明，能干嘛，怎么实现的，注意事项...   * @param xxx      参数1的使用说明， 能否为null   * @return 返回结果的说明， 不同情况下会返回怎样的结果   * @throws 异常类型   注明从此类方法中抛出异常的说明   */ &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;e. 构造方法注释&lt;/h4&gt; &lt;p&gt;在每个构造方法前面必须加上注释，注释模板如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**   * 构造方法的详细说明   * @param xxx      参数1的使用说明， 能否为null   * @throws 异常类型   注明从此类方法中抛出异常的说明   */ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而简单注解往往是需要工程师字节定义，在使用注解时应该注意一下几点：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;span style="box-sizing: border-box;color: rgb(74, 74, 74);font-size: 14px !important;"&gt;枚举类的各个属性值都要使用注解，枚举可以理解为是常量，通常不会发生改变，通常会被在多个地方引用，对枚举的修改和添加属性通常会带来很大的影响。&lt;/span&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;保持排版整洁，不要使用行尾注释；双斜杠和星号之后要用1个空格分隔。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;int id  = 1 ; // 反例：不要使用行尾注释 //反例：换行符与注释之间没有缩进  int  age  = 18 ;  // 正例：姓名 String name ;  /**  * 1. 多行注释  *  * 2. 对于不同的逻辑说明，可以用空行分隔  */ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;无论是命名和注解，他们的目的都是为了让代码和工程师进行对话，增强代码的可读性，可维护性。优秀的代码往往能够见名知意，注解往往是对命名的补充和完善。命名太难了！&lt;/p&gt; &lt;p&gt;参考文献：《码出高效》 https://www.cnblogs.com/wangcp-2014/p/10215620.html https://qiita.com/KeithYokoma/items/2193cf79ba76563e3db6 https://google.github.io/styleguide/javaguide.html#s2.1-file-name&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:21:13 GMT</pubDate>
    </item>
    <item>
      <title>各种乱码原因及示例</title>
      <link>https://maruifu.cn/article/147</link>
      <content:encoded>&lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/07/6t23tcmheognaqnkgclma9sjr9.png" alt="alt" title="alt" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:13:11 GMT</pubDate>
    </item>
    <item>
      <title>Java 通过反射动态执行方法</title>
      <link>https://maruifu.cn/article/146</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;     public static void main(String[] args) throws  Exception{             Class clazz = Class.forName(&amp;quot;com.mantis.hc.sale.dto.response.ExamAssistCustomerQueryRespDTO&amp;quot;);         Class clazz1 = ExamAssistCustomerQueryRespDTO.class;          ExamAssistCustomerQueryRespDTO examAssistCustomerQueryRespDTO = new ExamAssistCustomerQueryRespDTO();         examAssistCustomerQueryRespDTO.setAccount(&amp;quot;qweqweqweqweqw&amp;quot;);          String  attachmenturl = (String) clazz.getMethod(&amp;quot;getAccount&amp;quot;).invoke(examAssistCustomerQueryRespDTO);//执行方法         String  attachmenturl1 = (String) clazz1.getMethod(&amp;quot;getAccount&amp;quot;).invoke(examAssistCustomerQueryRespDTO);//执行方法          System.out.println(attachmenturl);         System.out.println(attachmenturl1);      }  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:08:41 GMT</pubDate>
    </item>
    <item>
      <title>Java 检测无效下载地址（进度版）</title>
      <link>https://maruifu.cn/article/145</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List;  public class TestFile {          public static void main(String[] args) throws Exception{             // select group_concat(CONCAT('&amp;quot;',download_url,'&amp;quot;'))    from   table           List&amp;lt;String&amp;gt; list = Arrays.asList(         &amp;quot;https://maruifu.cn/pdf/static//702e126d6d714be0979ae27aaa59893b.pdf&amp;quot;         ,&amp;quot;https://maruifu.cn/pdf/static//98edd35b7e8a493cbeeda2bf4abeb8d8.pdf&amp;quot;         ,&amp;quot;https://maruifu.cn/pdf/static//92e2360fc7514bd191b1c248de8fda5d.pdf&amp;quot;         ,&amp;quot;https://maruifu.cn/pdf/static//85faf19d86084a938571907db4f83c4a.pdf&amp;quot;);         List&amp;lt;String&amp;gt; strs = new ArrayList&amp;lt;&amp;gt;();         for (int i = 0; i &amp;lt; list.size(); i++) {             printSchedule(  (i + 1)*100/list.size());             if(!&amp;quot;N&amp;quot;.equals(getFlile(list.get(i)))){                 strs.add(getFlile(list.get(i)));             }          }         System.out.println(&amp;quot;\n&amp;quot;+&amp;quot;不能下载地址列表:&amp;quot;+strs.toString());      }         public static String getFlile(String filepath)throws Exception{             URL pathUrl = new URL(filepath);             HttpURLConnection urlcon = (HttpURLConnection) pathUrl.openConnection();             if(urlcon.getResponseCode()&amp;gt;=400){                 return filepath;             }           return &amp;quot;N&amp;quot;;     }        private static int TOTLE_LENGTH = 100;     public static void printSchedule(int percent){         for (int i = 0; i &amp;lt; TOTLE_LENGTH + 10; i++) {             System.out.print(&amp;quot;\b&amp;quot;);         }         int now = TOTLE_LENGTH * percent / 100;         for (int i = 0; i &amp;lt; now; i++) {             System.out.print(&amp;quot;&amp;gt;&amp;quot;);         }         for (int i = 0; i &amp;lt; TOTLE_LENGTH - now; i++) {             System.out.print(&amp;quot; &amp;quot;);         }         System.out.print(&amp;quot;  &amp;quot; + percent + &amp;quot;%&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:04:00 GMT</pubDate>
    </item>
    <item>
      <title>Java 通过网络流转发文件到浏览器</title>
      <link>https://maruifu.cn/article/144</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;public void downloadVideoById(HttpServletRequest request, HttpServletResponse response) throws Exception {         logger.info(&amp;quot;下载请求start&amp;gt;&amp;gt;&amp;quot;);         String fileName = request.getParameter(&amp;quot;fileName&amp;quot;);//文件名         String filePath = request.getParameter(&amp;quot;filePath&amp;quot;);//文件名         try {             if (StringUtil.isEmpty(fileName) || StringUtil.isEmpty(filePath)) {                 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);                 response.setCharacterEncoding(&amp;quot;UTF-8&amp;quot;);                 response.setContentType(&amp;quot;application/json;charset=UTF-8&amp;quot;);                 response.getWriter().print(&amp;quot;参数错误，请联系管理员!&amp;quot;);                 response.flushBuffer();                 return;             }              URL pathUrl = new URL(filePath);             HttpURLConnection urlcon = (HttpURLConnection) pathUrl.openConnection();             if(urlcon.getResponseCode()&amp;gt;=400){                 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);                 response.setCharacterEncoding(&amp;quot;UTF-8&amp;quot;);                 response.setContentType(&amp;quot;application/json;charset=UTF-8&amp;quot;);                 response.getWriter().print(&amp;quot;文件不存在，请联系管理员!&amp;quot;);                 response.flushBuffer();                 return;             }             //获取输入流对象（用于读文件） 网络流             InputStream inputStream = new URL(filePath).openStream();             //本地流文件             // FileInputStream fis = new FileInputStream(new File(filePath));             //动态设置响应类型，根据前台传递文件类型设置响应类型             response.setContentType(&amp;quot;application/&amp;quot; + fileName.substring(fileName.lastIndexOf(&amp;quot;.&amp;quot;)+1));             //设置响应头,attachment表示以附件的形式下载，inline表示在线打开             response.setHeader(&amp;quot;content-disposition&amp;quot;, &amp;quot;attachment;fileName=&amp;quot; + URLEncoder.encode(fileName, &amp;quot;UTF-8&amp;quot;));//下载时浏览器显示的名称             //获取输出流对象（用于写文件）             ServletOutputStream os = response.getOutputStream();             //下载文件,使用spring框架中的FileCopyUtils工具             FileCopyUtils.copy(inputStream, os);          } catch (Exception e) {             logger.error(&amp;quot;下载失败 start &amp;gt;&amp;gt;&amp;quot;,e);             response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);             response.setCharacterEncoding(&amp;quot;UTF-8&amp;quot;);             response.setContentType(&amp;quot;application/json;charset=UTF-8&amp;quot;);             response.getWriter().print(&amp;quot;下载失败，请联系管理员!&amp;quot;);             response.flushBuffer();         }       } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:03:00 GMT</pubDate>
    </item>
    <item>
      <title>Java 修改文件名</title>
      <link>https://maruifu.cn/article/143</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;        public static void main(String[] args) throws Exception {                String  oldPath =  &amp;quot;/Users/admin/test/jmeter/123.log&amp;quot;;             String  newPath =  &amp;quot;/Users/admin/test/jmeter/123/qweqweqweq.log&amp;quot;;              //创建指定的路径             File directory = new File(newPath);             //获取文件夹 路径             String courseFile = directory.getParent();             File file = new File(courseFile );//             if(!file.exists()){//如果文件夹不存在                 file.mkdir();//创建文件夹             }             File oldFile = new File(oldPath );             File newFile = new File(newPath );             //重命名             System.out.println(oldFile.renameTo(newFile));                                 } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 07 Jul 2020 15:00:13 GMT</pubDate>
    </item>
    <item>
      <title>群晖Docker安装chevereto图床</title>
      <link>https://maruifu.cn/article/142</link>
      <content:encoded>&lt;h2&gt;Docker chevereto 准备的环境&lt;/h2&gt; &lt;p&gt;mysql 数据库  （我目前是使用的是&lt;code&gt;MariaDB 10&lt;/code&gt;,安装数据库不多做叙述，可以看我之前的教程）&lt;/p&gt; &lt;p&gt;我用的是 Navicat ，新建数据库 填写 chevereto 如图：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto6.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto6.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto6.png" /&gt;&lt;/p&gt; &lt;h2&gt;Docker chevereto的下载&lt;/h2&gt; &lt;p&gt;在群晖docker里面的注册表里面搜索 Chevereto ，我用的nmtan/chevereto下载，如果有让你选择标签的话默认就好，等待容器镜像下载完成。如图: &lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto1.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto1.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto1.png" /&gt;&lt;/p&gt; &lt;h2&gt;Docker chevereto的存储卷&lt;/h2&gt; &lt;p&gt;在群晖的docker目录里面建立子目录Chevereto，后面安装容器会挂载此目录作为图床的文件存储目录,注意文件名的大小写 &lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto2.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto2.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto2.png" /&gt;&lt;/p&gt; &lt;h2&gt;Docker chevereto的配置&lt;/h2&gt; &lt;p&gt;容器镜像下载完成后，点击下载的镜像文件名的小箭头，查看该容器该如何进行配置，docker其实大部分都有配置介绍，多看看自己也会配置 &lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto3.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto3.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto3.png" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto4.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto4.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto4.png" /&gt;&lt;/p&gt; &lt;p&gt;双击该镜像进行安装，容器名称随意填写，内存限制根据实际需要填写，点击高级设置，启用自动重新启动打钩，卷设置里面点击添加文件夹，选择你刚刚在docker目录下创建的 chevereto目录，后面装载路径填写【/var/www/html/images】，不能有空格，请注意，然后在到端口设置，本地端口设置为10000，容器端口不需要修改，后面进行docker的环境配置，&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto5.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto5.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto5.png" /&gt;&lt;/p&gt; &lt;p&gt;点击启用后，可以使用http:群晖地址:10000 进行访问，设置 相关的信息&lt;/p&gt; &lt;p&gt;有时候会提示群晖  没有 对 images 文件夹的写入权限 ，后面对/volume1/docker/chevereto 赋予权限即可！&lt;/p&gt; &lt;p&gt;也可以从ssh 里面直接赋予所有权限&lt;/p&gt; &lt;pre&gt;&lt;code&gt;chmod +r 777 chevereto &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Docker chevereto修改上传大小&lt;/h2&gt; &lt;p&gt;http://域名:10000/dashboard/settings/image-upload&lt;/p&gt; &lt;h3&gt;获取root权限&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;admin@XiaoMageNAS:~$ sudo -i root@XiaoMageNAS:~#   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;查看运行的docker容器&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;root@XiaoMageNAS:~# docker ps -a CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS                     PORTS                                             NAMES 327c9776e0d3        nmtan/chevereto:latest             &amp;quot;docker-php-entrypoi…&amp;quot;   17 hours ago        Up 5 hours                 0.0.0.0:10000-&amp;gt;80/tcp                             Chevereto 7a30c3437280        oldiy/music-player-docker:latest   &amp;quot;docker-php-entrypoi…&amp;quot;   13 days ago         Up 13 days                 0.0.0.0:32769-&amp;gt;264/tcp, 0.0.0.0:32768-&amp;gt;9000/tcp   oldiy-music-player-docker1 1c8b79caaee6        luodaoyi/kms-server:1112           &amp;quot;/bin/sh -c 'vlmcsdm…&amp;quot;   8 weeks ago         Exited (255) 7 weeks ago                                                     luodaoyi-kms-server1 root@XiaoMageNAS:~#   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;复制到群晖本地目录&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;root@XiaoMageNAS:~# docker cp  327c9776e0d3:/var/www/html/.htaccess /volume1/docker/ root@XiaoMageNAS:~#   &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;修改配置文件&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;vi /volume1/docker/.htaccess &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;# Disable server signature ServerSignature Off    # Disable directory listing (-indexes), Multiviews (-MultiViews) and enable Follow system links (+FollowSymLinks) Options -Indexes Options -MultiViews  &amp;lt;IfModule mod_rewrite.c&amp;gt;      RewriteEngine On      # If you have problems with the rewrite rules remove the &amp;quot;#&amp;quot; from the following RewriteBase line     # You will also have to change the path to reflect the path to your Chevereto installation     # If you are using alias is most likely that you will need this.     #RewriteBase /      # 404 images     # If you want to have your own fancy &amp;quot;image not found&amp;quot; image remove the &amp;quot;#&amp;quot; from RewriteCond and RewriteRule lines     # Make sure to apply the correct paths to reflect your current installation     RewriteCond %{REQUEST_FILENAME} !-f     RewriteRule images/.+\.(gif|jpe?g|png|bmp) - [NC,L,R=404]     #RewriteRule images/.+\.(gif|jpe?g|png|bmp) content/images/system/default/404.gif [NC,L]      RewriteCond %{REQUEST_FILENAME} !-f     RewriteCond %{REQUEST_FILENAME} !-d     RewriteCond %{REQUEST_URI} !\.(css|js|html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpe?g|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|swf|tar|tif|tiff|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$ [NC]     RewriteRule . index.php [L]     #修改上传文件大小增加以下 配置  最大支持 32M 根据自己情况配置     php_value post_max_size 64M     php_value upload_max_filesize 32M  &amp;lt;/IfModule&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;复制到容器目录里面&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;docker cp /volume1/docker/.htaccess 327c9776e0d3:/var/www/html/ &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后进入到docker容器管理里面重新启动即可解除2m上传限制&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/chevereto7.png" alt="https://img.maruifu.com/images/blog/2020/06/chevereto7.png" title="https://img.maruifu.com/images/blog/2020/06/chevereto7.png" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 14 Jun 2020 07:41:03 GMT</pubDate>
    </item>
    <item>
      <title>群晖NAS利用Docker容器搭建KMS激活服务器实现激活windows系统和office</title>
      <link>https://maruifu.cn/article/141</link>
      <content:encoded>&lt;h2&gt;前言&lt;/h2&gt; &lt;p&gt;Windows系统中能够通过KMS进行激活的一般称为VL版,即VOLUME授权版。我们可以自行搭建KMS激活服务器，实现每180天一次的自动激活，使得系统一直保持激活状态。这次就跟大家分享一下如何利用群晖NAS的Docker容器套件搭建KMS服务器，并演示如何利用我们自己的KMS服务器激活Windows操作系统与Microsoft Office。&lt;/p&gt; &lt;h2&gt;操作步骤&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;先到套件中心安装Docker套件&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/2.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/2.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;安装好以后打开Docker在注册表这里搜索：KMS，选择第一个，点击【下载】&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/3.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/3.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;选择一个版本，我这里就选择:1112&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/4.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/4.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;稍等片刻下载完成，完成后再到【映像】选择到我们刚才下载的点击【启动】&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/5.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/5.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;开始创建容器，这里点击【高级设置】&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/6.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/6.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;在【网络】这里勾选【使用与Docker Host相同的网络】&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/7.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/7.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;最后确认一下即可点击【应用】，应用后自动启动容器。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;这样我们的KMS服务器就算搭建好了，KMS服务器默认端口号为：1688，IP的话就是我们NAS的IP。想要外网也可以用的话就可以使用内网穿透或者端口映射。下面以Windows10专业工作站版为例演示一下如何使用KMS激活系统。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;这里首先我们先判断一下我们的Windows是什么版本，管理员身份运行命令提示符，输入：&lt;code&gt;wmic os get caption&lt;/code&gt; 看到我这里是Windows 10 专业工作站版&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/8.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/8.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;打开：&lt;a href="https://technet.microsoft.com/en-us/library/jj612867.aspx" target="_blank"&gt;https://technet.microsoft.com/en-us/library/jj612867.aspx&lt;/a&gt; 找到对应系统版本的KEY，例如Windows10 专业工作站版的KEY为：NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/9.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/9.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;依旧管理员身份打开命令提示符，键入如下命令（对应您自己的情况修改以下命令内容。nas.maruifu.cn为您的NAS的IP地址，NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J为操作系统对应的KEY）：&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;slmgr /skms nas.zeruns.tech  slmgr /ipk NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J  slmgr /ato &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/10.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/10.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查看一下系统的激活状态&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/11.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/11.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;这样我们就完成了利用自建的KMS服务器激活我们的操作系统，接下来就是激活Microsoft Office，这里我以Microsoft Office 2019 VOL 专业版为例演示操作。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;首先先确认下我们的Office是否为VOL版，方法如下（请您根据自身情况更改以下命令）：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;管理员身份运行命令提示符，输入 cd C:\Program Files\Microsoft Office\Office16 切换目录 （这里请根据您自己的Office版本更改相应路径），再输入&lt;code&gt;cscript ospp.vbs /dstatus&lt;/code&gt; 。可以看到这里有VL字样即为VOL版&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/12.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/12.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;然后开始激活，输入如下命令：&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-bash"&gt;cd C:\Program Files\Microsoft Office\Office16  cscript ospp.vbs /sethst:nas.zeruns.tech  cscript ospp.vbs /act &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;最后看到Product activation successful字样即为激活成功。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/13.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/13.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/14.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/14.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;查看一下Microsoft Office的激活状态&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/2020/06/15.png" target="_blank"&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/06/15.png" alt="img" title="img" /&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 13 Jun 2020 15:01:25 GMT</pubDate>
    </item>
    <item>
      <title>Docker镜像pull不下来最终解决方法</title>
      <link>https://maruifu.cn/article/140</link>
      <content:encoded>&lt;p&gt;pull镜像wordpress下来，但是出现如下错误：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# docker pull wordpress:latest     Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaitin headers) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看日志，发现出现如下错误：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#tailf /var/log/messages Aug 19 16:46:29 docker02 dockerd: time=&amp;quot;2019-08-19T16:46:29.157861585+08:00&amp;quot; level=warning msg=&amp;quot;Error getting v2 registry: Get https://registry.docker-cn.com/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&amp;quot; Aug 19 16:46:29 docker02 dockerd: time=&amp;quot;2019-08-19T16:46:29.157965774+08:00&amp;quot; level=info msg=&amp;quot;Attempting next endpoint for pull after error: Get https://registry.docker-cn.com/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&amp;quot; Aug 19 16:46:44 docker02 dockerd: time=&amp;quot;2019-08-19T16:46:44.158651847+08:00&amp;quot; level=warning msg=&amp;quot;Error getting v2 registry: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&amp;quot; Aug 19 16:46:44 docker02 dockerd: time=&amp;quot;2019-08-19T16:46:44.158907684+08:00&amp;quot; level=info msg=&amp;quot;Attempting next endpoint for pull after error: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&amp;quot; Aug 19 16:46:44 docker02 dockerd: time=&amp;quot;2019-08-19T16:46:44.159189201+08:00&amp;quot; level=error msg=&amp;quot;Handler for POST /v1.40/images/create returned error: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;发现是因为docker加速器超时导致pull不下来 查看加速器：/etc/docker/daemon.json&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# cat /etc/docker/daemon.json  { &amp;quot;registry-mirrors&amp;quot;: [&amp;quot;https://registry.docker-cn.com&amp;quot;], &amp;quot;insecure-registries&amp;quot;: [&amp;quot;10.0.0.12:5000&amp;quot;] } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;导致此问题产生，主要是因为国家把docker国外镜像hub封掉了，导致镜像pull不下来，为此，改用国内的镜像&lt;/p&gt; &lt;pre&gt;&lt;code&gt;# cat /etc/docker/daemon.json  { &amp;quot;registry-mirrors&amp;quot;: [&amp;quot;https://docker.mirrors.ustc.edu.cn/&amp;quot;,&amp;quot;https://hub-mirror.c.163.com&amp;quot;,&amp;quot;https://registry.docker-cn.com&amp;quot;], &amp;quot;insecure-registries&amp;quot;: [&amp;quot;10.0.0.12:5000&amp;quot;] } #systemctl restart docker # docker pull wordpress:latest latest: Pulling from library/wordpress 1ab2bdfe9778: Pulling fs layer  1448c64389e0: Pulling fs layer  4b8a4e62b444: Pulling fs layer  9eb9d1e8e241: Pulling fs layer  d20b2d19292c: Pull complete  023060ea5930: Pull complete  a7fa99bc84ac: Pull complete  138ec8da18f2: Pull complete  cd4dae5ac262: Pull complete  c90eff48869a: Pull complete  1bc49f4d3a43: Pull complete  e3bb2b10f58d: Pull complete  fd7b454ec570: Pull complete  6096f23889f4: Pull complete  81072ed817d5: Pull complete  ecce7df16ad3: Pull complete  f4475635015e: Pull complete  bad34b7324ad: Pull complete  890f49d5ad8a: Pull complete  7e4ee285d305: Pull complete  Digest: sha256:6566a68d0c613304aa11255d98aba6e29c5fa8cd8497064639343956a4c7d2b1 Status: Downloaded newer image for wordpress:latest docker.io/library/wordpress:latest  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以正常Pull下来了。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 13 Jun 2020 14:33:22 GMT</pubDate>
    </item>
    <item>
      <title>群晖 MariaDB10 开启远程登录</title>
      <link>https://maruifu.cn/article/139</link>
      <content:encoded>&lt;p&gt;群晖安装 MariaDB10 后，默认仅支持本机连接，也就是说，你的局域网电脑是连接不上的，如果需要局域网连接，需要做处理。&lt;/p&gt; &lt;p&gt;环境：群晖6.2、MariaDB10&lt;/p&gt; &lt;p&gt;处理方法：&lt;/p&gt; &lt;p&gt;1、使用 ssh 登录到群晖&lt;/p&gt; &lt;p&gt;2、进入 MariaDB 默认安装目录&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd /volume1/@appstore/MariaDB10/usr/local/mariadb10/bin  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3、使用 root 登录 MariaDB，然后进行修改&lt;/p&gt; &lt;pre&gt;&lt;code&gt;admin@XiaoMageNAS:~$ sudo -i root@XiaoMageNAS:~# cd  /volume1/@appstore root@XiaoMageNAS:/volume1/@appstore# ls AudioStation  DownloadStation  MailPlus-Server  NoteStation   PHP7.0                      TextEditor    WebStation CloudStation  ffmpeg           MariaDB10        PDFViewer     PHP7.2                      transmission CloudSync     Git              Node.js_v12      Perl          PythonModule                VideoStation Docker        MailClient       Node.js_v8       PhotoStation  SynologyApplicationService  WebDAVServer root@XiaoMageNAS:/volume1/@appstore# cd /volume1/@appstore/MariaDB10/usr/local/mariadb10/bin root@XiaoMageNAS:/volume1/@appstore/MariaDB10/usr/local/mariadb10/bin#  root@XiaoMageNAS:/volume1/@appstore/MariaDB10/usr/local/mariadb10/bin# ./mysql -u root -p Enter password:  Welcome to the MariaDB monitor.  Commands end with ; or \g. Your MariaDB connection id is 14 Server version: 10.3.21-MariaDB Source distribution  Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.  Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.  MariaDB [(none)]&amp;gt; use mysql Database changed MariaDB [mysql]&amp;gt; update user set host = '%' where user = 'root'; ERROR 1062 (23000): Duplicate entry '%-root' for key 'PRIMARY' MariaDB [mysql]&amp;gt; select host,user from user; +-----------+------+ | host      | user | +-----------+------+ | %         | root | | 127.0.0.1 | root | | ::1       | root | +-----------+------+ 3 rows in set (0.000 sec)  MariaDB [mysql]&amp;gt; FLUSH PRIVILEGES; Query OK, 0 rows affected (0.001 sec)  MariaDB [mysql]&amp;gt;   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中 host 为 % 表示不限制IP，你也可以设置具体的IP地址，或者网段 192.168.1.% 这样。 另外，上面出现的 ERROR 1062 (23000): Duplicate entry '%-root' for key 'PRIMARY' 不予理会，其意思是 host 为主键，不能设置重复的值。所以我们后来的查询中，host 还是3个不同的值。刚刚执行的 update 语句只成功修改了数据库中一条数据。&lt;/p&gt; &lt;p&gt;4、不需要重启服务，即可连接登录。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 13 Jun 2020 14:28:00 GMT</pubDate>
    </item>
    <item>
      <title>1万亿元特别国债</title>
      <link>https://maruifu.cn/article/138</link>
      <content:encoded>&lt;p&gt;5月22日披露的政府工作报告显示，中国将&lt;strong&gt;发行1万亿元（人民币，下同）抗疫特别国债。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;作为特殊时期的特殊举措，&lt;strong&gt;1万亿元特别国债即将发行的消息迅速登上了微博热搜。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;小伙伴们都很关心：&lt;strong&gt;特别国债是什么？为什么要发行？怎么发行？老百姓能买吗？是否值得买&lt;/strong&gt;今天就来和大家说道说道。&lt;/p&gt; &lt;h2&gt;01  关于特别国债&lt;/h2&gt; &lt;p&gt;这次的特别国债顾名思义：&lt;strong&gt;抗疫&lt;/strong&gt;，就是为了应对新冠肺炎疫情影响，由中央财政统一发行的特殊国债。&lt;/p&gt; &lt;p&gt;发行的原因也很简单：由于疫情影响，&lt;strong&gt;财政收入下降，但支出变多，所以需要发行特别国债，弥补资金缺口。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;这也是我们国家时隔13年之后，又一次发行特别国债。&lt;/p&gt; &lt;h2&gt;02 怎么发行？&lt;/h2&gt; &lt;p&gt;虽然目前只是提出了发行的计划，具体的发行方式还没有正式的说明。&lt;/p&gt; &lt;p&gt;但我们可以从历史中两次发行特别国债中找找规律。&lt;/p&gt; &lt;p&gt;历史上&lt;strong&gt;我国在1998年和2007年发行过2次特别国债。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;第一次是在1998年，中国发行了2700亿元的特别国债，面向中国工商银行、中国农业银行、中国银行和中国建设银行定向发行。&lt;/p&gt; &lt;p&gt;第二次是在2007年，中国发行了1.55万亿元的特别国债采用的是&lt;strong&gt;定向发行和公开发行相结合，其中0.2万亿元向社会公众发行。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;从之前经验来看，特别国债主要面向各大银行等金融机构进行定向发行，&lt;strong&gt;可能会有&lt;/strong&gt; &lt;strong&gt;少部分针对个人投资者的公开发行&lt;/strong&gt;，但是额度不会太高，大概率需要去 &lt;strong&gt;各大商业银行柜台或者线上系统进行抢购。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;但是据我预计：这次大概率是要面向公众开放的。&lt;/p&gt; &lt;p&gt;毕竟这是抗疫特别国债，全国老百姓参与了抗疫的过程，购买抗疫特别国债也是一种爱国主义的表达方式。&lt;/p&gt; &lt;p&gt;如果感兴趣，可以留意下一步发行的具体安排，以及各大银行的官方消息。大概率是能抢购的。&lt;/p&gt; &lt;h2&gt;03 利率是多少？&lt;/h2&gt; &lt;p&gt;根据历史情况看：1998年发行的特别国债利率为7.2%，介于当年的3年期储蓄国债利率（7.11%）和5年期储蓄国债利率之间（7.86%）。&lt;/p&gt; &lt;p&gt;2007年发行的特别国债利率，利率在4.2%-4.5%之间，当年五年期储蓄国债利率也在4.5%左右。&lt;/p&gt; &lt;p&gt;可以看出，&lt;strong&gt;特别国债利率与储蓄国债利率其实差别不大，基本上&lt;/strong&gt; &lt;strong&gt;特别国债的利率在三年期-五年期国债的利率中间。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当前的储蓄国债有三年期和五年期，三年期的利率为4%，五年期的利率为4.27%，此次发行的一万亿特别国债是10年期的，&lt;strong&gt;年化利率大概率会在4%-4.2%的水平。&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;04 值得购买吗？&lt;/h2&gt; &lt;p&gt;一般情况下，值不值得买，就考虑&lt;strong&gt;风险&lt;/strong&gt;、&lt;strong&gt;收益率&lt;/strong&gt;和&lt;strong&gt;流动性&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;从风险来看，国债是由中央政府发行，信用级别最高，&lt;strong&gt;没有什么风险&lt;/strong&gt;；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;就目前国债在二级市场的表现来看，流动性也是很不错的。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;从收益来看，国债收益率看起来不是那么高，甚至还不如5年期的国债，但是胜在&lt;strong&gt;能锁死利率。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;之前我说过利率未来大概率是下行状态，现在能十年锁死4%左右的收益率，还是相当不错的一次理财机会了。&lt;/p&gt; &lt;p&gt;而且此次&lt;strong&gt;特别国债还免征利息所得税&lt;/strong&gt;，外加上历史意义。在我看来还是有较大的吸引力的。&lt;/p&gt; &lt;p&gt;但是还是要提醒大家一句：特别国债不好买！是需要抢购的！！如果没买到也不用着急。&lt;/p&gt; &lt;p&gt;据悉，&lt;strong&gt;6月10日将正式开发售今年的第一批储蓄国债，国债买的机会很多，收益、安全性也都差不多。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;多看看银行官网（主要是五大行）的消息，买国债还是有希望的。只要是银行官网释放消息，一般各行的网上银行和柜台都是能购买国债的&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 29 May 2020 02:26:25 GMT</pubDate>
    </item>
    <item>
      <title>NAS 篇二：外网访问NAS上面的应用</title>
      <link>https://maruifu.cn/article/136</link>
      <content:encoded>&lt;h1&gt;一 ，申请公网IP(动态IP)&lt;/h1&gt; &lt;blockquote&gt; &lt;p&gt;在北方联通公网IP多一些,在南方电信公网IP多一些&lt;/p&gt; &lt;p&gt;北漂的我自己用的 正好是北京联通宽带,当初在咸鱼办理的七百多办理下来五百多.&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;申请公网IP&lt;/h2&gt; &lt;p&gt;一开始跟客服说客服说办理专网才有公网IP,申请失败!(客服可能理解错了,要的不是固定的公网IP,有钱当然随意了)&lt;/p&gt; &lt;p&gt;后又拨打客服沟通说要弄摄像头弄nas,帮转一下技术客服,然后给技术上说,然后四五分钟后重启就好了,&lt;/p&gt; &lt;h2&gt;验证公网IP&lt;/h2&gt; &lt;p&gt;登录光猫后台查看IP&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/04/ublb4qjmnihrvpq08kqjdes2ao.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;输入地址 www.ipip.net 查看IP&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/04/topdhnf020gjmqqavbovmsd6ov.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;发现两个IP一致说明公网IP 开通好了,记住那个VLAN ID 3961,我的是北京的是3961 ,地域不一样这个也不一样后面会用到&lt;/p&gt; &lt;h1&gt;二 , 光猫桥接&lt;/h1&gt; &lt;p&gt;光猫集成了很多功能，身兼数职，除了最基本的光电转换功能外，还集成了路由功能、DHCP服务、NAT、IPTV、WIFI功能等。而光猫的硬件只能满足家庭网络的基本需求，如果将光猫作为家庭网络的中心节点，由于光猫的性能问题，现在基本都是智能家具,笔记本,PC电脑,床头灯手机,天猫精灵,小爱同学,摄像机,智能插座,IPAD等等光设备就十几个,我打游戏时 经常460,作为一级宽带运营商不应该出现的问题,一开始购买联通宽带也是这个原因,不像是鹏博士,长城宽带那样二三级运营商&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;二三级运营商的运营模式大概是这样的 比如从1级运营商那里买1000兆宽带,然后往外卖 ,卖的时候宣称是100兆宽带,应该卖给10个人就够了,让每个人最大能达到100兆,假如每个人都用 最大能到1000兆,这时候没问题,但是他不会只卖给10个人,他会卖给100个人,如果这个时候里面只有同时10个人上网,那没有问题,每个人都能达到100兆,假如100个人同时上网,平均分配,每个人才会10M,因为总的1000M不变.这也就是为啥晚上的时候 你家的网络会慢的原因了!(当然没我说的这么简单,他可能会根据地域,算法 去优化这种网络带宽的分配策略)&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;首先登陆自己的光猫后台&lt;/h2&gt; &lt;p&gt;查看自己的光猫的连接宽带的账号密码 账号会显示,密码不知道的 可以按f12 打开检查&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2020/04/vargefp0iejadrq9de47eo5ptf.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;点击弹出框的左上角的鼠标箭头,选中密码,然后把&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;password&amp;quot;   修改为 &amp;lt;input type=&amp;quot;text&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这样密码就显示出来了 记住这个账号&lt;/p&gt; &lt;h2&gt;进入管理员模式&lt;/h2&gt; &lt;p&gt;用电脑直接通过网线连接光猫,不要经过路由器&lt;/p&gt; &lt;p&gt;我的联通光猫 是 ZXHN F477V2&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;192.168.1.1/hidden_version_switch.gch ，选 default version，密码 CUAdmin&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;机器自动重启之后就可以进 192.168.1.1/cu.html 了， 选管理员账户，密码 CUAdmin&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;进去之后新建 internet bridge，vlan 选项记得选 改 tag，然后在 vlan_id 里填 3961(有的不是3961, 上面说过不知道的看上面)&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;保存设置之后 重启就可以了&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;另外，如果有问题要恢复初始状态，重新回到 hidden_version_switch.gch 里选 beijing 配置 然后在配置页面里输入宽带账号就可以了&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;不知道的咋弄的联系客服让维修师傅帮你修改一下也可以!&lt;/p&gt; &lt;h2&gt;路由器拨号&lt;/h2&gt; &lt;p&gt;用电脑连接路由器路由器连接光猫&lt;/p&gt; &lt;p&gt;路由器选择宽带拨号PPPoE&lt;/p&gt; &lt;p&gt;输入前面查看的账号密码,这个时候查看路由器WAN口的IP地址,和之前通过 www.ipip.net 查看的IP地址是否一致,一致就说明可以了!&lt;/p&gt; &lt;h2&gt;路由器端口转发&lt;/h2&gt; &lt;p&gt;找到转发设置&lt;/p&gt; &lt;p&gt;可以端口转发,也可以DMZ主机,两个区别的是一个是指定端口转发,一个是所有接口转发.我这里因为后面部署好多应用,数据库啥的,我这里选择的是用的DMZ主机&lt;/p&gt; &lt;p&gt;DMZ主机地址填写自己的NAS的内网IP地址就可以了&lt;/p&gt; &lt;p&gt;这个时候直接访问自己的外网地址就可以了!&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 04 Apr 2020 06:49:45 GMT</pubDate>
    </item>
    <item>
      <title>NAS 篇一：记录下自己的第一台NAS系</title>
      <link>https://maruifu.cn/article/135</link>
      <content:encoded>&lt;h1&gt;购买理由&lt;/h1&gt; &lt;p&gt;处于以下考虑我有了人生中的第一台NAS&lt;/p&gt; &lt;ol&gt; &lt;li&gt;我之前购买了一台小米家庭云盘,之前是备份照片,离线下载,智能摄像机录像存储用的 ,后来感觉 也只能干这些,下载东西需要安装客户端,不能直接对外分享,不支持PC操作,尤其是MAC(公司给配了一台MAC),关键是有两个T的监控级内置硬盘,,和购买内存差不多少了,因为当时是众筹还很便宜,  在一个技术交流群里面有个人要购买,正好卖给他了.用了一年多便宜一百块钱.&lt;/li&gt; &lt;li&gt;我的阿里云服务器太慢了,换了mac笔记本后,之前我一直用的FoxMail邮箱里面的笔记,发现MAC客户端不支持,之前的笔记只能一点点复制出来,索性搭建了一个开源的笔记,发现有个为知笔记(多端支持同步支持makedown语法正式我想要的) 推荐挺好的,搭建好了,太卡了 页面打开半天,当然和我服务器有关系.&lt;/li&gt; &lt;li&gt;过去两年内各大硬盘的关停和限速.&lt;/li&gt; &lt;li&gt;可以作为家里的小型服务器使用。&lt;/li&gt; &lt;li&gt;自己心里种草了，自己也是个爱折腾的人，我的人生两大定律 &amp;quot;一切向钱看&amp;quot;,&amp;quot;活着就要折腾&amp;quot;,这是促成这个得主要原因&lt;/li&gt; &lt;/ol&gt; &lt;h1&gt;设备采购&lt;/h1&gt; &lt;p&gt;网上搜了一下 主要是推荐了三个品牌 群晖,威联通,铁威马各有各的特点总结三句话&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;可玩性推荐群晖&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;安全性推荐威联通&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;性价比推荐铁威马&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;我开始选择了铁威马,购买后发现声音有点大,北京租房的我只能在一个屋里面,虽然我睡眠质量比较好,毕竟还有对象在,体验两天,自费退回去了.后来选择了群辉,比铁威马多了近一千块&lt;/p&gt; &lt;h1&gt;系统安装&lt;/h1&gt; &lt;p&gt;搞定连上电源、网线，点击开机键。然后打开路由器地址查看终端管理,打开局域网地址，按照提示下一步下一步，设置好内容就可以了，十分钟左右就结束。安装完毕进入系统界面.&lt;/p&gt; &lt;h1&gt;软件安装&lt;/h1&gt; &lt;p&gt;我们买NAS的最主要需求：&lt;/p&gt; &lt;p&gt;1.文件云存储、云备份&lt;/p&gt; &lt;p&gt;2.照片云管理&lt;/p&gt; &lt;p&gt;3.影音文件云管理&lt;/p&gt; &lt;p&gt;4.云笔记&lt;/p&gt; &lt;p&gt;5.远程下载&lt;/p&gt; &lt;p&gt;6.其他高阶应用&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 04 Apr 2020 04:59:10 GMT</pubDate>
    </item>
    <item>
      <title>linux上安装Docker(非常简单的安装方法)</title>
      <link>https://maruifu.cn/article/134</link>
      <content:encoded>&lt;p&gt;Docker的三大核心概念：镜像、容器、仓库&lt;/p&gt; &lt;p&gt;镜像：类似虚拟机的镜像、用俗话说就是安装文件。&lt;/p&gt; &lt;p&gt;容器：类似一个轻量级的沙箱，容器是从镜像创建应用运行实例，&lt;/p&gt; &lt;p&gt;可以将其启动、开始、停止、删除、而这些容器都是相互隔离、互不可见的。&lt;/p&gt; &lt;p&gt;仓库：类似代码仓库，是Docker集中存放镜像文件的场所。&lt;/p&gt; &lt;p&gt;简单介绍一下在CentOS上安装Docker。 前置条件：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;64-bit 系统  kernel 3.10+ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;1.检查内核版本，返回的值大于3.10即可。&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;$ uname -r &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;2.使用 sudo 或 root 权限的用户登入终端。&lt;/h2&gt; &lt;h2&gt;3.确保yum是最新的&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;  $ yum update &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;4.添加 yum 仓库&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;tee /etc/yum.repos.d/docker.repo &amp;lt;&amp;lt;-'EOF' [dockerrepo] name=Docker Repository baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/ enabled=1 gpgcheck=1 gpgkey=https://yum.dockerproject.org/gpg EOF &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;5.安装 Docker&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# yum install -y docker-engine &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装成功后，使用docker version命令查看是否安装成功，安装成功后------如下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# docker version Client:  Version:      17.05.0-ce  API version:  1.29  Go version:   go1.7.5  Git commit:   89658be  Built:        Thu May  4 22:06:25 2017  OS/Arch:      linux/amd64 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;6.启动docker&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;  $systemctl start docker.service &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;7.验证安装是否成功(有client和service两部分表示docker安装启动都成功了)&lt;/h2&gt; &lt;p&gt;使用docker version命令查看&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# docker version Client:  Version:      17.05.0-ce  API version:  1.29  Go version:   go1.7.5  Git commit:   89658be  Built:        Thu May  4 22:06:25 2017  OS/Arch:      linux/amd64  Server:  Version:      17.05.0-ce  API version:  1.29 (minimum version 1.12)  Go version:   go1.7.5  Git commit:   89658be  Built:        Thu May  4 22:06:25 2017  OS/Arch:      linux/amd64  Experimental: false  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;8.设置开机自启动&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; $ sudo systemctl enable docker &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;到此为止docker就完全安装好了。&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 25 Mar 2020 15:42:09 GMT</pubDate>
    </item>
    <item>
      <title>从 Windows 过度到 Mac 必备快捷键对照表</title>
      <link>https://maruifu.cn/article/127</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;换MAC了,好多不习惯,特意整理了一下与Windows快捷键的区别!&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;Mac 键盘符号&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;图标&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;⌘&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇧&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇪&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Caps Lock&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; = &lt;kbd&gt;Alt&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌃&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↩&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌫&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⌦&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↑&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;上箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;↓&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;下箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;←&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;左箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;→&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;右箭头&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇞&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; = &lt;kbd&gt;Page Up&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇟&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; = &lt;kbd&gt;Page Down&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;Home&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;←&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;End&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Fn&lt;/kbd&gt; + &lt;kbd&gt;→&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇥&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt; = &lt;kbd&gt;右制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⇤&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Tab&lt;/kbd&gt; = &lt;kbd&gt;左制表符&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⎋&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Esc&lt;/kbd&gt; = &lt;kbd&gt;Escape&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;⏏&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;电源开关键&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;`&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示版本控制常用操作菜单弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示当前文件选择目标弹出层，弹出层中有很多目标可以进行选择&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;查询所选对象/变量被引用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;IntelliJ IDEA 根据光标所在问题，提供快速修复选择，光标放在的位置不同提示的结果也不同&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Insert&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码自动生成，如生成对象的 set / get 方法，构造函数，toString() 等&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;切换当前已打开的窗口中的子视图，比如Debug窗口中有Output、Debugger等子视图，用此快捷键就可以在子视图中切换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的前一个方法名位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前光标跳转到当前文件的后一个方法名位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示对应数值的选项卡，其中 1 是 Project 用得最多&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本查找&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在当前文件进行文本替换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;撤销&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;G&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到指定行数位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Y&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标所在行 或 删除选中的行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;D&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;D&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制光标所在行 或 复制选择内容，并把复制内容插入光标位置下面&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;方向键上&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展选中范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;E&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;显示最近打开的文件记录列表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入的 &lt;strong&gt;类名&lt;/strong&gt; 查找类文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;插入自定义动态代码模板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;P&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;P&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;方法参数提示显示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Q&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;当前位置变量、方法的 Documentation 内容显示&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前往当前光标所在的方法的父类的方法 / 接口定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;进入光标所在的方法/变量的接口或是定义处，等效于 &lt;code&gt;Ctrl + 左键单击&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;注释光标所在行代码，会根据当前不同文件类型使用不同的注释符号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F1&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在光标所在的错误代码处显示错误信息&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中文件 / 文件夹，使用助记符设定 / 取消书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出当前文件结构层，可以在弹出的层上直接输入，进行筛选&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;基础代码补全，默认在 Windows 系统上被输入法占用，需要进行修改，建议修改为 &lt;code&gt;Ctrl + 逗号&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Fn&lt;/kbd&gt;+ &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标后面的单词或是中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;BackSpace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;删除光标前面的单词或是中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;定位到对应数值的书签位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件标题上，弹出该文件路径&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的左侧开头位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标跳转到当前单词 / 中文句的右侧开头位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;预设中没有该快捷键&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向前效果&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;预设中没有该快捷键&lt;/td&gt;&lt;td align="left"&gt;等效于鼠标滚轮向后效果&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command + F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;弹出书签显示层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消缩进&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;开始新一行。光标所在行下空出一行，光标定位到新行位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Shift + 左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在打开的文件名上按此快捷键，可以关闭当前打开文件&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Alt + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择 / 添加 task&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键双击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键双击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选择被双击的单词 / 中文句，按住不放，可以同时选择其他单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向上移动&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;移动光标所在行向下移动&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;L&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;格式化代码，可以对当前文件和整个包目录使用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;优化导入的类，可以对当前文件和整个包目录使用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码弹出环绕选项弹出层&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;逗号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开 IntelliJ IDEA 系统设置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标所在行上空出一行，光标定位到新行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上一个操作的地方&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;前进到上一个操作的地方&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容查找整个项目 或 指定目录内文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;R&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;根据输入内容替换对应内容，范围为整个项目 或 指定目录内文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;J&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动将下一行合并到当前行末尾&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;取消撤销&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;W&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;方向键下&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;递进式取消选择代码块。可选中光标所在的单词或段落，连续按会在原有选中的基础上再扩展取消选中范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;N&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;O&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;通过文件名定位 / 打开文件 / 目录，打开目录需要在输入的内容后面多加一个正斜杠&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;U&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对选中的代码进行大 / 小写轮流转换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;T&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;对当前类生成单元测试类，如果已经存在的单元测试类则可以进行选择&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;复制当前文件磁盘路径到剪贴板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;B&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到类型声明处&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;/&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;代码块注释&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的顶部中括号位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;]&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;选中从光标所在位置到它的底部中括号位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;加号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;展开所有代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;减号&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;折叠所有代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F7&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;高亮显示所有该选中文本，按Esc高亮消失&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑器最大化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;自动结束代码，行末自动添加分号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Backspace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Backspace&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;退回到上次修改的地方&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Control&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;1,2,3...9&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;快速添加指定数值的书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左键单击&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;把光标放在某个类变量上，按此快捷键可以直接定位到该类中&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;左方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的左侧开头位置，同时选中该单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;右方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;在代码文件上，光标跳转到当前单词 / 中文句的右侧开头位置，同时选中该单词 / 中文句&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;前方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到上一个方法前面，调整方法排序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;后方向键&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;光标放在方法名上，将方法移动到下一个方法前面，调整方法排序&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Ctrl + Shift + Alt&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;V&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;无格式黏贴&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Command&lt;/kbd&gt; + &lt;kbd&gt;;&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;打开当前项目设置&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;Other&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;Win 快捷键&lt;/th&gt;&lt;th align="left"&gt;Mac 快捷键&lt;/th&gt;&lt;th align="left"&gt;介绍&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F2&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;跳转到下一个高亮错误 或 警告位置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F4&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;编辑源&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F11&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F3&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;添加书签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;F12&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;回到前一个工具窗口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;缩进&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;&lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;&lt;kbd&gt;ESC&lt;/kbd&gt;&lt;/td&gt;&lt;td align="left"&gt;从工具窗口进入代码文件窗口&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Sat, 21 Mar 2020 10:58:27 GMT</pubDate>
    </item>
    <item>
      <title>【HTML打印】HTML直接调用window下的打印机并执行打印任务（简单打印任务生成）</title>
      <link>https://maruifu.cn/article/125</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;今天有个朋友问我咋调用打印机,打印页面。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt; &amp;lt;head&amp;gt;     &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;     &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;     &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;ie=edge&amp;quot;&amp;gt;     &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;css/page/index.css&amp;quot;&amp;gt;     &amp;lt;title&amp;gt;window调用打印接口&amp;lt;/title&amp;gt;     &amp;lt;style&amp;gt;         #pr{width:100px;height:40px;line-height:40px;text-align:center;background:#ccc;}         .box{width:600px; }     &amp;lt;/style&amp;gt; &amp;lt;/head&amp;gt; &amp;lt;body&amp;gt;     &amp;lt;div class=&amp;quot;box&amp;quot;&amp;gt;        &amp;lt;p&amp;gt;  &amp;lt;table style=&amp;quot;width:100%;&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;1&amp;quot; bordercolor=&amp;quot;#000000&amp;quot;&amp;gt;   &amp;lt;tbody&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td colspan=&amp;quot;6&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      物资出库信息     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      领料单号     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;span&amp;gt;申请时间&amp;lt;/span&amp;gt;&amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      领料库房     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;span&amp;gt;领料部门&amp;lt;/span&amp;gt;&amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      车辆编号     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      领料人     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      库管员     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      出库时间     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td colspan=&amp;quot;6&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      备注     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      物质编号     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      &amp;lt;span&amp;gt;物质名称&amp;lt;/span&amp;gt;&amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      计量单位     &amp;lt;/td&amp;gt;     &amp;lt;td style=&amp;quot;text-align:center;&amp;quot;&amp;gt;      物资单     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;    &amp;lt;tr&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;     &amp;lt;td&amp;gt;      &amp;lt;br /&amp;gt;     &amp;lt;/td&amp;gt;    &amp;lt;/tr&amp;gt;   &amp;lt;/tbody&amp;gt;  &amp;lt;/table&amp;gt; &amp;lt;/p&amp;gt;     &amp;lt;/div&amp;gt;     &amp;lt;div id=&amp;quot;pr&amp;quot;&amp;gt;点击打印&amp;lt;/div&amp;gt; &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; &amp;lt;script&amp;gt;     function $(selector){         return document.querySelector(selector);     }     //获取整个页面     $(&amp;quot;#pr&amp;quot;).onclick =function(){         window.print();     }       /* 实现打印全部页面（也可以打印局部页面 - 看需求） ----         我是只打印baby里边的内容        获取我们定义的id     */     $(&amp;quot;#pr&amp;quot;).onclick =function(){         var oldHtml = $(&amp;quot;body&amp;quot;).innerHTML;          var printbox = $(&amp;quot;.box&amp;quot;).innerHTML;         $(&amp;quot;body&amp;quot;).innerHTML = printbox;         window.print();         $(&amp;quot;body&amp;quot;).innerHTML = oldHtml;       } &amp;lt;/script&amp;gt;    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我的博客即将同步至腾讯云+社区，邀请大家一同入驻：https://cloud.tencent.com/developer/support-plan?invite_code=182do5sqcmvp7&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 15 Mar 2020 07:35:41 GMT</pubDate>
    </item>
    <item>
      <title>mysql 替换字段部分内容</title>
      <link>https://maruifu.cn/article/124</link>
      <content:encoded>&lt;p&gt;[mysql]replace的用法（替换某字段部分内容） [mysql]replace的用法&lt;/p&gt; &lt;p&gt;1.replace into replace into table (id,name) values('1','aa'),('2','bb') 此语句的作用是向表table中插入两条记录。如果主键id为1或2不存在就相当于 insert into table (id,name) values('1','aa'),('2','bb') 如果存在相同的值则不会插入数据&lt;/p&gt; &lt;p&gt;2.replace(object,search,replace) 把object中出现search的全部替换为replace select replace('www.163.com','w','Ww')---&amp;gt;WwWwWw.163.com 例：把表table中的name字段中的aa替换为bb update table set name=replace(name,'aa','bb')&lt;/p&gt; &lt;p&gt;3.UPDATE更新一个字段中的的部分内容&lt;/p&gt; &lt;p&gt;现在有一条记录的字段是“abcdefg&amp;quot;,现在我只想将该字段中的c改为C，update语句应该怎么写&lt;/p&gt; &lt;p&gt;update 表名 set 字段1 = replace(字段1,'c','C')&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 01 Mar 2020 06:01:19 GMT</pubDate>
    </item>
    <item>
      <title>msyql全局搜索字段所在位置</title>
      <link>https://maruifu.cn/article/123</link>
      <content:encoded>&lt;p&gt;全局搜索字段所在位置&lt;/p&gt; &lt;pre&gt;&lt;code&gt; SELECT COLUMN_NAME,TABLE_SCHEMA,TABLE_NAME FROM information_schema.COLUMNS WHERE COLUMN_NAME='字段名字' +---------------+--------------+---------------+ | COLUMN_NAME   | TABLE_SCHEMA | TABLE_NAME    | +---------------+--------------+---------------+ | contacts_name | dxxxf_v3    | fledts        | | contacts_name | dxxxf_v3    | losdftions     | | contacts_name | dxxxf_v3    | orgafsizatsdons | | contacts_name | pxxnf      | afdts        | | contacts_name | sxxmens     | lafions     | | contacts_name | pxxmedfs     | sgdszations | +---------------+--------------+---------------+ ————————————————  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 01 Mar 2020 06:00:40 GMT</pubDate>
    </item>
    <item>
      <title>Linux 服务器上如何通过 Shell 脚本一键部署 SpringBoot 应用</title>
      <link>https://maruifu.cn/article/122</link>
      <content:encoded>&lt;p&gt;Linux 服务器上如何通过 Shell 脚本一键部署 SpringBoot 应用&lt;/p&gt; &lt;p&gt;springboot 是默认集成Tomcat容器的，将项目打包成jar包库、使用Java直接启动jar包（非spring boot也可以）&lt;/p&gt; &lt;h3&gt;首先需要在服务器端安装jdk、maven、git&lt;/h3&gt; &lt;p&gt;&lt;a href="https://mp.weixin.qq.com/s/uzmZzeJADs4aReJ8XchCzg" target="_blank"&gt;点我看maven安装教程&lt;/a&gt; &lt;a href="https://mp.weixin.qq.com/s/M41yKPA13rujCdU76O8S5w" target="_blank"&gt;点我看git安装教程&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;想要SpringBoot项目使用maven打包成jar包需先在项目中的pom添加build插件,代码如下&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; &amp;lt;build&amp;gt;         &amp;lt;plugins&amp;gt;             &amp;lt;plugin&amp;gt;                 &amp;lt;artifactId&amp;gt;maven-dependency-plugin&amp;lt;/artifactId&amp;gt;                 &amp;lt;configuration&amp;gt;                     &amp;lt;outputDirectory&amp;gt;${project.build.directory}/libs&amp;lt;/outputDirectory&amp;gt;                     &amp;lt;excludeTransitive&amp;gt;false&amp;lt;/excludeTransitive&amp;gt;                     &amp;lt;stripVersion&amp;gt;false&amp;lt;/stripVersion&amp;gt;                     &amp;lt;includeScope&amp;gt;compile&amp;lt;/includeScope&amp;gt;                     &amp;lt;!--&amp;lt;includeScope&amp;gt;runtime&amp;lt;/includeScope&amp;gt;--&amp;gt;                 &amp;lt;/configuration&amp;gt;                 &amp;lt;executions&amp;gt;                     &amp;lt;execution&amp;gt;                         &amp;lt;id&amp;gt;copy-dependencies&amp;lt;/id&amp;gt;                         &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;                         &amp;lt;goals&amp;gt;                             &amp;lt;goal&amp;gt;copy-dependencies&amp;lt;/goal&amp;gt;                         &amp;lt;/goals&amp;gt;                         &amp;lt;configuration&amp;gt;                             &amp;lt;!-- &amp;lt;outputDirectory&amp;gt;libs&amp;lt;/outputDirectory&amp;gt; --&amp;gt;                             &amp;lt;excludeTransitive&amp;gt;false&amp;lt;/excludeTransitive&amp;gt;                             &amp;lt;stripVersion&amp;gt;true&amp;lt;/stripVersion&amp;gt;                         &amp;lt;/configuration&amp;gt;                     &amp;lt;/execution&amp;gt;                 &amp;lt;/executions&amp;gt;             &amp;lt;/plugin&amp;gt;         &amp;lt;/plugins&amp;gt;          &amp;lt;/build&amp;gt;              &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此插件是指将项目所依赖的jar，打包的时候打包到libs目录下，一遍到时候编写shell脚本读取依赖的jar&lt;/p&gt; &lt;h3&gt;接下来就可以编写shell脚本了（get源码的方式有很多种，直接上传上去也行。本文将使用Git在服务器端直接拉取源码，编译打包，启动）&lt;/h3&gt; &lt;p&gt;主要修改 proc ,SOURCE_HOME,APP_LOG,PROFILES_ACTIVE 文件就好!&lt;/p&gt; &lt;pre&gt;&lt;code&gt; #打包完后的jar名称，替换成你自己项目的名称，该名称可以在maven项目的pom中配置 proc=&amp;quot;wechat&amp;quot; #项目源码的目录地址（初始可能需要自己从Git拉下来） SOURCE_HOME=&amp;quot;/usr/local/publicwx/publicWechat&amp;quot; #日志地址 APP_LOG=&amp;quot;$SOURCE_HOME/target/catalina.base_IS_UNDEFINED/logs/log_info.log&amp;quot;  #环境配置 用户配置开发(dev)，测试(test)，生产(prod)的配置文件，避免频繁改动 PROFILES_ACTIVE=&amp;quot;spring.profiles.active=dev&amp;quot;   #JVM启动参数，关于JVM调优这里不介绍，感兴趣的可以自行百度 JVM调优 JAVA_OPTS=&amp;quot;-server -Xms512M -Xmx512M -Xss256k -Xmn256m -XX:SurvivorRatio=4 -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M  -XX:CMSInitiatingOccupancyFraction=90 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=0 -XX:CMSFullGCsBeforeCompaction=100 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true&amp;quot;  psid=0  #检查进程是否存在 checkpid() {    javaps=$(pgrep -f &amp;quot;$proc&amp;quot;)    if [ -n &amp;quot;$javaps&amp;quot; ]; then       psid=$javaps    else       psid=0    fi }   #编写启动方法 start() {    checkpid     if [ $psid -ne 0 ]; then       echo &amp;quot;================================&amp;quot;       echo &amp;quot;warn: $proc already started! (pid=$psid)&amp;quot;       echo &amp;quot;================================&amp;quot;    else       echo &amp;quot;Starting $proc ...&amp;quot;    #到项目源码目录       cd $SOURCE_HOME    #输出，准备获取最新代码       echo -n &amp;quot;git pull source ,please wait .....&amp;quot;    #获取最新代码，此列只在目录所在分支pull       #若想部署指定分支代码，可以在脚本调用参数中添加一个变量，用git checkout ${targer_branch}       git pull               #输出，最新代码已拉取完毕，准备打包       echo -n &amp;quot;mvn package source ,please wait .....&amp;quot;               #maven打包命令，此处特别注意是 —U ,是指引用快照版本的jar（引用自己的项目）每次都更新最新的。       mvn clean package -Dmaven.test.skip=true       #打包成功后默认是在启动项目的target目录下。       cd target       #输出，准备启动       echo -n $&amp;quot;Starting $proc:&amp;quot;               #循环加载所需的jar，此处和2的pom配置有关       for name in *.jar       do         APP_CLASS=&amp;quot;$name&amp;quot;       done               #启动脚本，--spring.profiles.active=   用于设置环境所使用的配置文件       JAVA_CMD=&amp;quot;java &amp;quot;$JAVA_OPTS&amp;quot; -jar &amp;quot;$APP_CLASS&amp;quot; --&amp;quot;$PROFILES_ACTIVE&amp;quot; &amp;amp;&amp;quot;       #后台运行               $JAVA_CMD &amp;amp;       sleep 1       checkpid       if [ $psid -ne 0 ]; then       echo &amp;quot;======================================&amp;quot;          echo &amp;quot;$proc Start Success! (pid=$psid)[OK]&amp;quot;          echo &amp;quot;======================================&amp;quot;       else          echo &amp;quot;[Failed]&amp;quot;       fi    fi  }   #查看日志 showlog() {    tail -f $APP_LOG }    #停用项目 stop() {    checkpid     if [ $psid -ne 0 ]; then       echo -n &amp;quot;Stopping $proc ...(pid=$psid) &amp;quot;       kill -9 $psid        if [ $? -eq 0 ]; then          echo &amp;quot;[OK]&amp;quot;       else          echo &amp;quot;[Failed]&amp;quot;       fi            checkpid       if [ $psid -ne 0 ]; then          stop       fi    else       echo &amp;quot;================================&amp;quot;       echo &amp;quot;warn: $proc is not running&amp;quot;       echo &amp;quot;================================&amp;quot;    fi }   #项目状态 status() {    checkpid    if [ $psid -ne 0 ];  then       echo &amp;quot;$proc is running! (pid=$psid)&amp;quot;    else       echo &amp;quot;$proc is not running&amp;quot;    fi }   #设置脚本参数，启动的时候可以采用./脚本名称.sh start/stop/restart/log/status等参数 case &amp;quot;$1&amp;quot; in   start)     start     ;;   stop)     stop     ;;   log)     showlog     ;;   status)     status     ;;   restart)     stop     start     ;; esac  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 25 Nov 2019 15:58:00 GMT</pubDate>
    </item>
    <item>
      <title>centos7 安装 mysql 详解</title>
      <link>https://maruifu.cn/article/121</link>
      <content:encoded>&lt;h2&gt;查看linux系统版本&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# cat /etc/redhat-release   查看操作系统版本 CentOS Linux release 7.6.1810 (Core)  [root@maruifu ~]# uname -r      查看系统内核版本 3.10.0-957.21.3.el7.x86_64  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;下载&lt;/h2&gt; &lt;p&gt;下载地址 : https://dev.mysql.com/downloads/mysql/&lt;/p&gt; &lt;p&gt;centos7 选择   Red Hat Enterprise Linux / Oracle Linux&lt;/p&gt; &lt;p&gt;版本就自己根据自己系统下载就好了&lt;/p&gt; &lt;p&gt;可以选择 RPM Bundle 使用wget 下载 mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@nfs_client ~]# wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;也可以细化下载，下载须要的mysql组件，有4个：分别是 server、client、common、libs&lt;/p&gt; &lt;h2&gt;卸载旧版本的MySql （没有的话，则跳过此步骤）&lt;/h2&gt; &lt;p&gt;1、查看旧版本MySql &lt;code&gt;rpm -qa | grep mysql&lt;/code&gt; 将会列出旧版本MySql的组件列表，如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]#  rpm -qa | grep mysql mysql-community-libs-5.7.28-1.el7.x86_64 mysql-community-server-5.7.28-1.el7.x86_64 mysql-community-common-5.7.28-1.el7.x86_64 mysql-community-client-5.7.28-1.el7.x86_64 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt; 2、逐个删除掉旧的组件       使用命令rpm -e --nodeps {-file-name}进行移除操作，移除的时候可能会有依赖，要注意一定的顺序。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;rpm -e --nodeps mysql-community-libs-5.7.28-1.el7.x86_64 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装&lt;/h2&gt; &lt;h3&gt;解压&lt;/h3&gt; &lt;p&gt;[root@maruifu tools]# pwd /usr/local/tools [root@maruifu tools]#  tar xvf mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar&lt;/p&gt; &lt;h3&gt;卸载&lt;/h3&gt; &lt;p&gt;centos7自带的mariadb-lib&lt;/p&gt; &lt;ol&gt; &lt;li&gt;[root@maruifu tools]&lt;em&gt;# rpm -qa|grep mariadb&lt;/em&gt;&lt;/li&gt; &lt;li&gt;mariadb-libs-5.5.56-2.el7.x86_64&lt;/li&gt; &lt;li&gt;[root@maruifu tools]&lt;em&gt;# rpm -e mariadb-libs-5.5.56-2.el7.x86_64 --nodeps&lt;/em&gt;&lt;/li&gt; &lt;/ol&gt; &lt;h3&gt;安装&lt;/h3&gt; &lt;p&gt;这里我们只安装mysql-server服务，只需要安装如下4个软件包即可，&lt;/p&gt; &lt;p&gt;使用命令rpm -ivh {-file-name}进行安装操作。&lt;/p&gt; &lt;p&gt;&lt;code&gt;**注：ivh中， i-install安装；v-verbose进度条；h-hash哈希校验**&lt;/code&gt;&lt;/p&gt; &lt;p&gt;按照依赖关系依次安装rpm包 依赖关系依次为common→libs→client→server&lt;/p&gt; &lt;pre&gt;&lt;code class="language-mysql"&gt;1.  rpm -ivh  mysql-community-common-5.7.28-1.el7.x86_64.rpm 2.  rpm -ivh  mysql-community-libs-5.7.28-1.el7.x86_64.rpm 3.  rpm -ivh  mysql-community-client-5.7.28-1.el7.x86_64.rpm 4.  rpm -ivh  mysql-community-server-5.7.28-1.el7.x86_64.rpm &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;特殊情况1&lt;/h4&gt; &lt;p&gt;在阿里云ECS云服务器上安装mysql5.7，当安装 mysql-community-server-5.7.28-1.el7.x86_64.rpm 时报错，报错如下&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# rpm -ivh mysql-community-server-5.7.28-1.el7.x86_64.rpm warning: mysql-community-server-5.7.28-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY error: Failed dependencies:         libaio.so.1()(64bit) is needed by mysql-community-server-5.7.28-1.el7.x86_64         libaio.so.1(LIBAIO_0.1)(64bit) is needed by mysql-community-server-5.7.28-1.el7.x86_64         libaio.so.1(LIBAIO_0.4)(64bit) is needed by mysql-community-server-5.7.28-1.el7.x86_64          &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解决法案就是：安装libaio&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# yum -y install libaio &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;安装libaio后，再重新安装一次mysql-community-server-5.7.28-1.el7.x86_64.rpm，此时就能正常安装了&lt;/p&gt; &lt;h4&gt;特殊情况2&lt;/h4&gt; &lt;p&gt;比如解决了“特殊情况1”，但在启动mysql的时候，启动不起来，或启动后，去查找临时密码，使用命令没反应。查看日志mysqld.log(可在/etc/my.cnf中查找到mysqld.log的配置位置)，报如下错误，此时怎么解决？&lt;/p&gt; &lt;p&gt;[ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.user' doesn't exist&lt;/p&gt; &lt;p&gt;1&amp;gt;先通过rpm -e --nodeps xxx  卸载掉server，卸载后删除datadir目录，&lt;/p&gt; &lt;p&gt;2&amp;gt;卸载后查看 /etc/my.cnf 中，datadir的配置情况，将datedir目录删除，&lt;/p&gt; &lt;p&gt;3&amp;gt;最后通过命令rpm -ivh xxx 重新安装server,此时就能正常使用mysql了&lt;/p&gt; &lt;p&gt;命令代码如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# rpm -e --nodeps mysql-community-server-5.7.22-1.el7.x86_64  [root@maruifu ~]# cat /etc/my.cnf  datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock   [root@maruifu ~]# cd /var/lib [root@maruifu lib]# rm -rf mysql [root@maruifu ~]# rpm -ivh mysql-community-server-5.7.22-1.el7.x86_64.rpm [root@maruifu ~]# systemctl start mysqld.service [root@maruifu ~]# ps -ef|grep mysql mysql    21001     1  0 Nov22 ?        00:00:00 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid root     22218 20782  0 00:21 pts/1    00:00:00 grep --color=auto mysql root     24317 31119  0 Nov21 pts/0    00:00:00 mysql -u root -p  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;登录并创建MySql密码&lt;/h3&gt; &lt;h2&gt;启动MySql&lt;/h2&gt; &lt;p&gt;安装完后，使用命令 service mysqld start 或 systemctl start mysqld.service 启动MySQL服务。（如果mysql服务无法启动，就重启一下系统）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;systemctl start mysqld.service    启动mysql systemctl status mysqld.service  查看mysql状态 systemctl stop mysqld.service   关闭mysql  查看mysql进程 ps -ef|grep mysql 查看3306端口 netstat -anop|grep 3306 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;登陆mysql修改root密码&lt;/h2&gt; &lt;p&gt;由于MySQL5.7.4之前的版本中默认是没有密码的，登录后直接回车就可以进入数据库，进而进行设置密码等操作。其后版本对密码等安全相关操作进行了一些改变，在安装过程中，会在安装日志中生成一个临时密码。&lt;/p&gt; &lt;p&gt;怎么找到这个临时密码呢？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;grep 'temporary password' /var/log/mysqld.log  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;[root@maruifu ~]# grep 'temporary password' /var/log/mysqld.log 2019-11-21T08:07:27.278434Z 1 [Note] A temporary password is generated for root@localhost: rj3pj;g3XWIh  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;rj3pj;g3XWIh即为登录密码。使用这个随机密码登录进去，然后修改密码，使用命令：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mysql -uroot -p &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行下面的命令修改MySql root密码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;set password for root@localhost=password('12345678');  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在5.6后,mysql内置密码增强机制,低强度密码会报错:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ERROR 1819 (HY000): Your password does not satisfy the current policy requirements &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;更改策略，设置 validate_password_policy=0;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mysql&amp;gt; set global validate_password_length=1; Query OK, 0 rows affected (0.00 sec) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;修改密码&lt;/p&gt; &lt;pre&gt;&lt;code&gt;1. mysql&amp;gt; set password for root@localhost=password('TianTianIT12345'); 2. Query OK, 0 rows affected, 1 warning (0.00 sec) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;此时，虽然防火墙我时关着的，但root用户只能用于本机访问，不能用于远程访问，否则会报以下错误。因此，接下来要做的是授予root用户远程访问权限。&lt;/p&gt; &lt;p&gt;查看当前授予过的权限：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;use mysql; select user,host from user;  &lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;&lt;code&gt;mysql&amp;gt; use mysql; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A   Database changed mysql&amp;gt; select user,host from user; +---------------+-----------+ | user          | host      | +---------------+-----------+ | root          | %         | | mysql.session | localhost | | mysql.sys     | localhost | | root          | localhost | +---------------+-----------+ 4 rows in set (0.00 sec)   mysql&amp;gt; show grants; +---------------------------------------------------------------------+ | Grants for root@localhost                                           | +---------------------------------------------------------------------+ | GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION | | GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION        | +---------------------------------------------------------------------+ 2 rows in set (0.00 sec) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;授予root用户远程访问权限：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mysql&amp;gt; grant all privileges on *.* to root@'%' identified by 'TianTianIT12345'; Query OK, 0 rows affected, 1 warning (0.05 sec) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;刷新权限，使设置生效， OK。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mysql&amp;gt; flush privileges; Query OK, 0 rows affected (0.36 sec) &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 23 Nov 2019 11:31:43 GMT</pubDate>
    </item>
    <item>
      <title>linux 中nginx 的安装</title>
      <link>https://maruifu.cn/article/120</link>
      <content:encoded>&lt;h1&gt;linux 中nginx 的安装&lt;/h1&gt; &lt;p&gt;##序言&lt;/p&gt; &lt;p&gt;Nginx是lgor Sysoev为俄罗斯访问量第二的rambler.ru站点设计开发的。从2004年发布至今，凭借开源的力量，已经接近成熟与完善。&lt;/p&gt; &lt;p&gt;Nginx功能丰富，可作为HTTP服务器，也可作为反向代理服务器，邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模块扩展。&lt;/p&gt; &lt;p&gt;Nginx的稳定性、功能集、示例配置文件和低系统资源的消耗让他后来居上，在全球活跃的网站中有12.18%的使用比率，大约为2220万个网站。&lt;/p&gt; &lt;p&gt;牛逼吹的差不多啦，如果你还不过瘾，你可以百度百科或者一些书上找到这样的夸耀，比比皆是。&lt;/p&gt; &lt;h2&gt;Nginx常用功能&lt;/h2&gt; &lt;h3&gt;1、Http代理，反向代理：作为web服务器最常用的功能之一，尤其是反向代理。&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/11/47keoeb820i6er83saqv6hnc34.jpg" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;p&gt;Nginx在做反向代理时，提供性能稳定，并且能够提供配置灵活的转发功能。Nginx可以根据不同的正则匹配，采取不同的转发策略，比如图片文件结尾的走文件服务器，动态页面走web服务器，只要你正则写的没问题，又有相对应的服务器解决方案，你就可以随心所欲的玩。并且Nginx对返回结果进行错误页跳转，异常判断等。如果被分发的服务器存在异常，他可以将请求重新转发给另外一台服务器，然后自动去除异常服务器。&lt;/p&gt; &lt;h3&gt;2、负载均衡&lt;/h3&gt; &lt;p&gt;Nginx提供的负载均衡策略有2种：内置策略和扩展策略。内置策略为轮询，加权轮询，Ip hash。扩展策略，就天马行空，只有你想不到的没有他做不到的啦，你可以参照所有的负载均衡算法，给他一一找出来做下实现。上3个图，理解这三种负载均衡算法的实现 &lt;img src="https://img.maruifu.com/images/blog/2019/11/usjb7n7p26htcrv0v2qsuddugj.jpg" alt="alt" title="alt" /&gt; &lt;img src="https://img.maruifu.com/images/blog/2019/11/3fjqrmdqlmgr4r3kje71h73ver.jpg" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;3、web缓存&lt;/h3&gt; &lt;p&gt;Nginx可以对不同的文件做不同的缓存处理，配置灵活，并且支持FastCGI_Cache，主要用于对FastCGI的动态程序进行缓存。配合着第三方的ngx_cache_purge，对制定的URL缓存内容可以的进行增删管理。&lt;/p&gt; &lt;h3&gt;4、Nginx相关地址&lt;/h3&gt; &lt;p&gt;源码：https://trac.nginx.org/nginx/browser&lt;/p&gt; &lt;p&gt;官网：http://www.nginx.org/&lt;/p&gt; &lt;h2&gt;Nginx安装&lt;/h2&gt; &lt;h3&gt;安装依赖库&lt;/h3&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;安装gcc模块， 安装Nginx前，必须先确保安装了gcc环境。那么何为gcc?它是 Linux 下默认的 C/C++ 编译器，大部分 Linux 发行版中都是默认安装的。命令行输入&lt;code&gt;gcc -v&lt;/code&gt;，如果显示命令未找到或command not found，则代表没有安装gcc，需要安装上。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum install gcc-c++  &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;安装prce模块: PCRE(Perl Compatible Regular Expressions)是一个Perl库，包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式，所以需要在linux上安装pcre库。 地址 : http://www.pcre.org/&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;//下载`wget  https://ftp.pcre.org/pub/pcre/pcre-8.37.tar.gz  //解压 tar -zxvf pcre-8.37.tar.gz  //然后,进入包，配置： ./configure  //安装 make &amp;amp;&amp;amp; make install   &lt;/code&gt;&lt;/pre&gt; &lt;ol start="3"&gt; &lt;li&gt;安装openssl模块,OpenSSL 是一个强大的安全套接字层密码库，囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议，并提供丰富的应用程序供测试或其它目的使用。  nginx不仅支持http协议，还支持https（即在ssl协议上传输http），所以需要在linux安装openssl库。地址: http://www.openssl.org/&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;//下载  wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz   // 解压  tar -zxvf openssl-1.1.0e.tar.gz   //然后,进入包，配置： ./configure  //安装 make &amp;amp;&amp;amp; make install   &lt;/code&gt;&lt;/pre&gt; &lt;ol start="4"&gt; &lt;li&gt;安装gzip 模块, zlib库提供了很多种压缩和解压缩的方式，nginx使用zlib对http包的内容进行gzip，所以需要在linux上安装zlib库。 地址 http://www.zlib.net/&lt;/li&gt; &lt;/ol&gt; &lt;pre&gt;&lt;code&gt;//下载 wget http://prdownloads.sourceforge.net/libpng/zlib-1.2.11.tar.gz?download   //解压 tar -zxvf zlib-1.2.11.tar.gz?download   // 然后,进入包，配置： ./configure  //安装 make &amp;amp;&amp;amp; make install   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;也可以 yum安装&lt;/p&gt; &lt;pre&gt;&lt;code&gt;yum install -y pcre pcre-devel  yum install -y openssl openssl-devel  yum install -y zlib zlib-devel  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;安装Nginx并启动&lt;/h3&gt; &lt;p&gt;//创建一个文件夹&lt;/p&gt; &lt;pre&gt;&lt;code&gt;cd /usr/local/ mkdir server cd server/ //下载 :  wget http://nginx.org/download/nginx-1.7.8.tar.gz //解压 tar -xvf nginx-1.7.8.tar.gz //创建目录 mkdir nginx //&amp;gt;&amp;gt;安装一个第三方模块,可以打印输出一些东西，一般用于调试nginx的参数时使用(也可以不安装) //&amp;gt;&amp;gt;创建一个模块目录 用于放第三方模块 mkdir nginx-module //&amp;gt;&amp;gt;进入目录: cd nginx-module //&amp;gt;&amp;gt;下载: wget https://github.com/openresty/echo-nginx-module/archive/v0.60.tar.gz //&amp;gt;&amp;gt;解压: tar zxvf v0.60.tar.gz echo-nginx-module-0.60  //进入安装目录 cd nginx-1.7.8 //配置 ./configure --prefix=/usr/local/server/nginx --add-module=/usr/local/server/nginx-module/echo-nginx-module-0.60 --with-debug  源码的安装一般由有这三个步骤：配置(configure)、编译(make)、安装(make install) 一般在编译前加上一句： ./configure --prefix=/xxx/xxx 其中–prefix选项就是配置安装的路径，如果不配置该选项，安装后可执行文件默认放在/usr/local/bin，库文件默认放在/usr/local/lib，配置文件默认放在/usr/local/etc，其它的资源文件放在/usr/local/share，比较分散。 为了便于集中管理某个软件的各种文件，可以配置–prefix，如： ./configure --prefix=/usr/local/server/nginx --with-http_stub_status_module --with-http_ssl_module 可以把所有资源文件放在/usr/local/server/nginx的路径中，就不会分散了。 // 编译安装 make &amp;amp;&amp;amp; make install //进入启动目录 cd .. cd nginx/sbin/ //启动 ./nginx //这个时候可以访问本机IP了 &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置Nginx&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;打开配置文件 vi /usr/local/nginx/conf/nginx.conf &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;配置文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt; user  root; worker_processes  auto; error_log  logs/error.log  warn; pid        logs/nwarn.pid;  events {     worker_connections  1024; }  http {     include       mime.types;     default_type  application/octet-stream;      log_format  main  '$remote_addr - $remote_user [$time_local] &amp;quot;$request&amp;quot; '                       '$status $body_bytes_sent &amp;quot;$http_referer&amp;quot; '                       '&amp;quot;$http_user_agent&amp;quot; &amp;quot;$http_x_forwarded_for&amp;quot;';     access_log  logs/access.log  main;     sendfile        on;     tcp_nopush          on;     tcp_nodelay         on;     keepalive_timeout   65;     types_hash_max_size 2048;     client_max_body_size 20m;     client_body_buffer_size 20m;     gzip on;                     #开启gzip压缩功能     gzip_min_length 10k;         #设置允许压缩的页面最小字节数; 这里表示如果文件小于10个字节，就不用压缩，因为没有意义，本来就很小.     gzip_buffers 4 16k;          #设置压缩缓冲区大小，此处设置为4个16K内存作为压缩结果流缓存     gzip_http_version 1.1;       #压缩版本     gzip_comp_level 1;           #设置压缩比率，最小为1，处理速度快，传输速度慢；9为最大压缩比，处理速度慢，传输速度快; 这里表示压缩级别，可以是0到9中的任一个，级别越高，压缩就越小，节省了带宽资源，但同时也消耗CPU资源，所以一般折中为6     gzip_types text/plain text/css application/javascript text/javascript;      #制定压缩的类型,线上配置时尽可能配置多的压缩类型!     gzip_vary on;    #选择支持vary header；改选项可以让前端的缓存服务器缓存经过gzip压缩的页面; 这个可以不写，表示在传送数据时，给客户端说明我使用了gzip压缩i     include /usr/local/server/nginx/conf/conf.d/*.conf;   }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Nginx 常用命令&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;nginx -s reload            # 重新载入配置文件 nginx -s reopen            # 重启 Nginx nginx -s stop              # 停止 Nginx nginx -t                   # 检查配置文件nginx.conf nginx -s quit              #优雅停止nginx，有连接时会等连接请求完成再杀死worker进程   nginx -v                #查看版本 nginx  -c filename      #指定配置文件 nginx -h                # 查看帮助信息 nginx -s reopen          #重新打开日志文件，一般用于切割日志 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 20 Nov 2019 16:33:00 GMT</pubDate>
    </item>
    <item>
      <title>Java 服务线上问题排查思路与工具使用</title>
      <link>https://maruifu.cn/article/118</link>
      <content:encoded>&lt;h2&gt;前言&lt;/h2&gt; &lt;p&gt;Java 语言是当前互联网应用最为广泛的语言，作为一名 Java 程序猿，当业务相对比较稳定之后平常工作除了 coding 之外，大部分时间（70%~80%）是会用来排查突发或者周期性的线上问题。由于业务应用 bug（本身或引入第三方库）、内外部环境、底层硬件问题等原因，Java线上服务出现故障/问题几乎不可避免。例如，常见的现象包括部分请求超时、用户明显感受到系统发生卡顿等等。&lt;/p&gt; &lt;p&gt;尽管线上问题从系统表象来看非常明显，但排查深究其发生的原因还是比较困难的，因此对开发测试或者是运维的同学产生了许多的困扰。排查定位线上问题是具有一定技巧或者说是经验规律的，排查者如果对业务系统了解得越深入，那么相对来说定位也会容易一些。&lt;/p&gt; &lt;p&gt;不管怎么说，掌握 Java 服务线上问题排查思路并能够熟练排查问题常用工具/命令/平台是每一个 Java 程序猿进阶必须掌握的实战技能。笔者依据自己的 工作经验总结出一套基本的线上问题排查流程，同学们可以根据自己的实际工作情况进行归纳总结。&lt;/p&gt; &lt;h2&gt;Java 服务常见线上问题&lt;/h2&gt; &lt;p&gt;所有 Java 服务的线上问题从系统表象来看归结起来总共有四方面：CPU、内存、磁盘、网络。例如 CPU 使用率峰值突然飚高、内存溢出(泄露)、磁盘满了、网络流量异常、FullGC 等等问题。基于这些现象我们可以将线上问题分成两大类: 系统异常、业务服务异常。&lt;/p&gt; &lt;h3&gt;系统异常&lt;/h3&gt; &lt;p&gt;常见的系统异常现象包括: CPU 占用率过高、CPU上下文切换频率次数较高、磁盘满了、磁盘 I/O 过于频繁、网络流量异常（连接数过多）、系统可用内存长期处于较低值（导致 oom killer）等等。这些问题可以通过 top（cpu）、free（内存）、df（磁盘）、dstat（网络流量）、pstack、vmstat、strace（底层系统调用）等工具获取系统异常现象数据。&lt;/p&gt; &lt;p&gt;此外，如果对系统以及应用进行排查后，均未发现异常现象的根本原因，那么也有可能是因为外部基础设施 IAAS 平台所引发的问题。例如运营商网络或者云服务提供商偶尔可能也会发生一些故障问题，你的引用只有某个区域如广东用户访问系统时发生服务不可用现象，那么极有可能是这些原因导致的。今天，我司部署在阿里云华东地域的业务系统中午时分突然不能为广东地区用户提供正常服务，对系统进行各种排查均为发现任何问题。最后，通过查询阿里云公告得知原因是&amp;quot;广东地区电信线路访问华东地区互联网资源（包含阿里云华东1地域）出现网络丢包或者延迟增大的异常情况&amp;quot;。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/25jhi1papgg3rr4acnr0na2urk.png" alt="阿里云网络异常图片" title="阿里云网络异常图片" /&gt;&lt;/p&gt; &lt;h3&gt;业务服务异常&lt;/h3&gt; &lt;p&gt;常见的业务服务异常现象包括: PV 量过高、服务调用耗时异常、线程死锁、多线程并发问题、频繁进行 Full GC、异常安全攻击扫描等。&lt;/p&gt; &lt;h2&gt;问题定位&lt;/h2&gt; &lt;p&gt;我们一般会采用排除法，从外部排查到内部排查的方式来定位线上服务问题。&lt;/p&gt; &lt;p&gt;首先我们要排除其他进程（除主进程之外）可能引起的故障问题 其次排除业务应用可能引起的故障问题 最后可以考虑是否为运营商或者云服务提供商所引起的故障&lt;/p&gt; &lt;h3&gt;定位流程&lt;/h3&gt; &lt;h4&gt;系统异常排查流程&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/vl13osdtuohjip4vasks1t16nd.gif" alt="系统异常排查流程图片" title="系统异常排查流程图片" /&gt;&lt;/p&gt; &lt;h4&gt;业务应用排查流程&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/3r2r5cc4oahs9pop6gl9vaqn76.png" alt=" 业务应用排查流程" title=" 业务应用排查流程" /&gt;&lt;/p&gt; &lt;h3&gt;Linux 常用的性能分析工具&lt;/h3&gt; &lt;p&gt;Linux 常用的性能分析工具使用包括 : top（cpu）、free（内存）、df（磁盘）、dstat（网络流量）、pstack、vmstat、strace（底层系统调用）等。&lt;/p&gt; &lt;h4&gt;CPU&lt;/h4&gt; &lt;p&gt;CPU 是系统重要的监控指标，能够分析系统的整体运行状况。监控指标一般包括运行队列、CPU使用率和上下文切换等。&lt;/p&gt; &lt;p&gt;top命令是Linux下常用的 CPU 性能分析工具,能够实时显示系统中各个进程的资源占用状况,常用于服务端性能分析。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/4qlr7c6248gm2re7shbjgk5o3n.png" alt="" title="" /&gt;&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;top 命令显示了各个进程 CPU 使用情况，一般 CPU 使用率从高到低排序展示输出。其中 Load Average 显示最近1分钟、5分钟和15分钟的系统平均负载，上图各值为2.46，1.96，1.99。&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;我们一般会关注 CPU 使用率最高的进程，正常情况下就是我们的应用主进程。第七行以下：各进程的状态监控。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;pre&gt;&lt;code&gt;PID : 进程id USER : 进程所有者 PR : 进程优先级 NI : nice值。负值表示高优先级，正值表示低优先级 VIRT : 进程使用的虚拟内存总量，单位kb。VIRT=SWAP+RES RES : 进程使用的、未被换出的物理内存大小，单位kb。RES=CODE+DATA SHR : 共享内存大小，单位kb S : 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程 %CPU : 上次更新到现在的CPU时间占用百分比 %MEM : 进程使用的物理内存百分比 TIME+ : 进程使用的CPU时间总计，单位1/100秒 COMMAND : 进程名称 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;内存&lt;/h4&gt; &lt;p&gt;内存是排查线上问题的重要参考依据，内存问题很多时候是引起 CPU 使用率较高的见解因素。&lt;/p&gt; &lt;p&gt;系统内存：free 是显示的当前内存的使用，-m 的意思是M字节来显示内容。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;free -m &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/5m5qm5lhtaio6q0594q02eg85h.png" alt="" title="" /&gt;&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;部分参数说明：&lt;/p&gt; &lt;blockquote&gt; &lt;/blockquote&gt; &lt;p&gt;total 内存总数: 3790M&lt;/p&gt; &lt;blockquote&gt; &lt;/blockquote&gt; &lt;p&gt;used 已经使用的内存数: 1880M&lt;/p&gt; &lt;blockquote&gt; &lt;/blockquote&gt; &lt;p&gt;free 空闲的内存数: 118M&lt;/p&gt; &lt;blockquote&gt; &lt;/blockquote&gt; &lt;p&gt;shared 当前已经废弃不用,总是0&lt;/p&gt; &lt;blockquote&gt; &lt;/blockquote&gt; &lt;p&gt;buffers Buffer 缓存内存数: 1792M&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h4&gt;磁盘&lt;/h4&gt; &lt;p&gt;磁盘满了很多时候会连带引起系统服务不可用等问题&lt;/p&gt; &lt;pre&gt;&lt;code&gt;df -h &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/2vhps5cdcohdkpm6d9qsegosf3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;du -m /path &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/t9ejrtm970h6spvlu6l7qhuat8.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h4&gt;网络&lt;/h4&gt; &lt;p&gt;dstat 命令可以集成了 vmstat、iostat、netstat 等等工具能完成的任务。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;dstat -c  cpu情况       -d 磁盘读写       -n 网络状况       -l 显示系统负载       -m 显示形同内存状况       -p 显示系统进程信息       -r 显示系统IO情况 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/2ps3c4iecggudqt7ir8ji1e0l1.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h4&gt;其它&lt;/h4&gt; &lt;p&gt;vmstat：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;vmstat 2 10 -t &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;vmstat 是 Virtual Meomory Statistics（虚拟内存统计）的缩写, 是实时系统监控工具。该命令通过使用 knlist 子程序和 /dev/kmen 伪设备驱动器访问这些数据，输出信息直接打印在屏幕。&lt;/p&gt; &lt;p&gt;使用 vmstat 2 10 -t命令，查看 io 的情况 （第一个参数是采样的时间间隔数单位是秒，第二个参数是采样的次数）。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/2jn9li2m5ogulqbfsfbbsbtd5v.png" alt="" title="" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;r 表示运行队列(就是说多少个进程真的分配到CPU),b 表示阻塞的进程。   swpd 虚拟内存已使用的大小，如果大于0，表示你的机器物理内存不足了，如果不是程序内存泄露的原因，那么你该升级内存了或者把耗内存的任务迁移到其他机器。  free   空闲的物理内存的大小，我的机器内存总共4G，剩余120M左右。  buff   Linux/Unix系统是用来存储，目录里面有什么内容，权限等的缓存，我本机大概占用40多M  cache 文件缓存  si列表示由磁盘调入内存，也就是内存进入内存交换区的数量；  so列表示由内存调入磁盘，也就是内存交换区进入内存的数量   一般情况下，si、so的值都为0，如果si、so的值长期不为0，则表示系统内存不足，需要考虑是否增加系统内存。  bi 从块设备读入数据的总量（读磁盘）（每秒kb） bo 块设备写入数据的总量（写磁盘）（每秒kb） 随机磁盘读写的时候，这两个值越大((超出1024k)，能看到cpu在IO等待的值也会越大 这里设置的bi+bo参考值为1000，如果超过1000，而且wa值比较大，则表示系统磁盘IO性能瓶颈。  in 每秒CPU的中断次数，包括时间中断 cs(上下文切换Context Switch) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;strace：strace常用来跟踪进程执行时的系统调用和所接收的信号。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;strace -cp tid strace -T -p tid       -T 显示每一调用所耗的时间.     -p pid  跟踪指定的进程pid.      -v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.      -V 输出strace的版本信息. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/qs2b5tl6i2jvar8tpps27q9r6g.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h3&gt;JVM 定位问题工具&lt;/h3&gt; &lt;p&gt;在 JDK 安装目录的 bin 目录下默认提供了很多有价值的命令行工具。每个小工具体积基本都比较小，因为这些工具只是 jdk\lib\tools.jar 的简单封装。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/7oqeqpiss6jbtofikmcp9gdoj0.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;其中，定位排查问题时最为常用命令包括：jps（进程）、jmap（内存）、jstack（线程）、jinfo（参数）等。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;jps：查询当前机器所有JAVA进程信息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;jmap：输出某个 Java 进程内存情况（如产生那些对象及数量等）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;jstack：打印某个 Java 线程的线程栈信息&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;jinfo：用于查看 jvm 的配置参数&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;jps 命令&lt;/h4&gt; &lt;p&gt;jps 用于输出当前用户启动的所有进程 ID，当线上发现故障或者问题时，能够利用 jps 快速定位对应的 Java 进程 ID。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jps -l -m -m -l -l参数用于输出主启动类的完整路径 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/p0h1g1cfe6gunp4tb9j5h35m9m.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;当然，我们也可以使用 Linux 提供的查询进程状态命令，例如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ps -ef | grep tomcat &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们也能快速获取 Tomcat 服务的进程 id。&lt;/p&gt; &lt;h4&gt;jmap 命令&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;jmap -heap pid   输出当前进程JVM堆新生代、老年代、持久代等请情况，GC使用的算法等信息 jmap -histo:live {pid} | head -n 10  输出当前进程内存中所有对象包含的大小 jmap -dump:format=b,file=/usr/local/logs/gc/dump.hprof {pid} 以二进制输出档当前内存的堆情况，然后可以导入MAT等工具进行 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;JMap（Java Memory Map）可以输出所有内存中对象的工具，甚至可以将 VM 中的 heap，以二进制输出成文本。&lt;/p&gt; &lt;p&gt;jmap -heap pid：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jmap -heap pid   输出当前进程JVM堆新生代、老年代、持久代等请情况，GC使用的算法等信息 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;jmap 可以查看 JVM 进程的内存分配与使用情况，使用 的 GC 算法等信息。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/rnr829ocfojt2o45b87ud71gi3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/oa30jdt8hogvrp42n1383kjmet.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;jmap -histo:live {pid} | head -n 10：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jmap -histo:live {pid} | head -n 10  输出当前进程内存中所有对象包含的大小 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/7bn70res4uhbfoi09i45bj5no5.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;输出当前进程内存中所有对象实例数（instances）和大小（bytes），如果某个业务对象实例数和大小存在异常情况，可能存在内存泄露或者业务设计方面存在不合理之处。&lt;/p&gt; &lt;p&gt;jmap -dump：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jmap -dump:format=b,file=/usr/local/logs/gc/dump.hprof {pid}  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;-dump:formate=b,file= 以二进制输出当前内存的堆情况至相应的文件，然后可以结合 MAT 等内存分析工具深入分析当前内存情况。&lt;/p&gt; &lt;p&gt;一般我们要求给 JVM 添加参数 -XX:+Heap Dump On Out Of Memory Error OOM 确保应用发生 OOM 时 JVM 能够保存并 dump 出当前的内存镜像。当然如果你决定手动 dump 内存时，dump 操作占据一定 CPU 时间片、内存资源、磁盘资源等，因此会带来一定的负面影响。&lt;/p&gt; &lt;p&gt;此外，dump 的文件可能比较大,一般我们可以考虑使用zip命令对文件进行压缩处理，这样在下载文件时能减少带宽的开销。在下载 dump 文件完成之后，由于 dump 文件较大可将 dump 文件备份至制定位置或者直接删除，以释放磁盘在这块的空间占用。&lt;/p&gt; &lt;h4&gt;jstack 命令&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;printf '%x\n' tid   --&amp;gt;  10进制至16进制线程ID(navtive线程) %d 10进制 jstack pid | grep tid -C 30 --color    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;某 Java 进程 CPU 占用率高，我们想要定位到其中 CPU 占用率最高的线程。&lt;/p&gt; &lt;p&gt;（1） 利用 top 命令可以查出占 CPU 最高的线程 pid&lt;/p&gt; &lt;pre&gt;&lt;code&gt;top -Hp {pid} &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/02et9cvpiggb5o2pb1q5oa8bkb.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;（2） 占用率最高的线程 ID 为 6900，将其转换为16进制形式（因为 java native 线程以16进制形式输出）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;printf '%x\n' 6900 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/2s3o990gk2hg9pq7m8h3bv0ar3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;（3） 利用 jstack 打印出 Java 线程调用栈信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;jstack 6418 | grep '0x1af4' -A 50 --color &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/40i8aegv60jp0qc3scd4hjfa7l.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h4&gt;jinfo 命令&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;查看某个JVM参数值 jinfo -flag ReservedCodeCacheSize 28461 jinfo -flag MaxPermSize 28461 &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;jstat 命令&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;jstat -gc pid jstat -gcutil `pgrep -u admin java` &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;内存分析工具 MAT&lt;/h3&gt; &lt;h4&gt;什么是 MAT?&lt;/h4&gt; &lt;p&gt;MAT（Memory Analyzer Tool），一个基于 Eclipse 的内存分析工具，是一个快速、功能丰富的 JAVA heap 分析工具，它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析，快速的计算出在内存中对象的占用大小，看看是谁阻止了垃圾收集器的回收工作，并可以通过报表直观的查看到可能造成这种结果的对象。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/1pd8cl8o4cgcoq7icg70rp5d52.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;右侧的饼图显示当前快照中最大的对象。单击工具栏上的柱状图，可以查看当前堆的类信息，包括类的对象数量、浅堆（Shallow heap）、深堆（Retained Heap）。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;浅堆&lt;/strong&gt;表示一个对象结构所占用内存的大小。&lt;strong&gt;深堆&lt;/strong&gt;表示一个对象被回收后，可以真实释放的内存大小。&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;支配树（The Dominator Tree）：&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;列出了堆中最大的对象，第二层级的节点表示当被第一层级的节点所引用到的对象，当第一层级对象被回收时，这些对象也将被回收。这个工具可以帮助我们定位对象间的引用情况，垃圾回收时候的引用依赖关系&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;Path to GC Roots&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;被JVM持有的对象，如当前运行的线程对象，被systemclass loader加载的对象被称为GC Roots， 从一个对象到GC Roots的引用链被称为Path to GC Roots， 通过分析Path to GC Roots可以找出JAVA的内存泄露问题，当程序不在访问该对象时仍存在到该对象的引用路径。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;日志分析&lt;/h2&gt; &lt;h3&gt;GC 日志分析&lt;/h3&gt; &lt;h4&gt;GC 日志详细分析&lt;/h4&gt; &lt;p&gt;Java 虚拟机 GC 日志是用于定位问题重要的日志信息，频繁的 GC 将导致应用吞吐量下降、响应时间增加，甚至导致服务不可用。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/usr/local/gc/gc.log -XX:+UseConcMarkSweepGC &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们可以在 Java 应用的启动参数中增加  &lt;strong&gt;-XX:+PrintGCDetails&lt;/strong&gt; 可以输出 GC 的详细日志，例外还可以增加其他的辅助参数，如 -Xloggc 制定 GC 日志文件地址。如果你的应用还没有开启该参数,下次重启时请加入该参数。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/tohmo3d2vcgfmq0pnl5af512lh.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;上图为线上某应用在平稳运行状态下的GC日志截图。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;2017-12-29T18:25:22.753+0800: 73143.256: [GC2017-12-29T18:25:22.753+0800: 73143.257: [ParNew: 559782K-&amp;gt;1000K(629120K), 0.0135760 secs] 825452K-&amp;gt;266673K(2027264K), 0.0140300 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解析说明:&lt;/p&gt; &lt;pre&gt;&lt;code&gt; [2017-12-29T18:25:22.753+0800: 73143.256] ： 自JVM启动73143.256秒时发生本次GC. [ParNew: 559782K-&amp;gt;1000K(629120K), 0.0135760 secs] : 对新生代进行的GC，使用ParNew收集器，559782K是新生代回收前的大小,1000K是新生代回收后大小,629120K是当前新生代分配的内存总大小, 0.0135760 secs表示本次新生代回收耗时 0.0135760秒 [825452K-&amp;gt;266673K(2027264K), 0.0140300 secs]:825452K是回收堆内存大小,266673K是回收堆之后内存大小，2027264K是当前堆内存总大小,0.0140300 secs表示本次回收共耗时0.0140300秒 [Times: user=0.02 sys=0.00, real=0.02 secs] : 用户态耗时0.02秒,系统态耗时0.00,实际耗时0.02秒 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;无论是 minor GC 或者是 Full GC，我们主要关注 GC 回收实时耗时， 如 real=0.02secs，即 stop the world 时间，如果该时间过长，则严重影响应用性能。&lt;/p&gt; &lt;h4&gt;CMS GC 日志分析&lt;/h4&gt; &lt;p&gt;Concurrent Mark Sweep（CMS）是老年代垃圾收集器，从名字（Mark Sweep）可以看出，CMS 收集器就是“标记-清除”算法实现的，分为六个步骤：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;初始标记（STW initial mark）&lt;/li&gt; &lt;li&gt;并发标记（Concurrent marking）&lt;/li&gt; &lt;li&gt;并发预清理（Concurrent precleaning）&lt;/li&gt; &lt;li&gt;重新标记（STW remark）&lt;/li&gt; &lt;li&gt;并发清理（Concurrent sweeping）&lt;/li&gt; &lt;li&gt;并发重置（Concurrent reset）&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;其中初始标记（STW initial mark） 和 重新标记（STW remark）需要“Stop the World”。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;初始标记&lt;/strong&gt; ：在这个阶段，需要虚拟机停顿正在执行的任务，官方的叫法 STW（Stop The Word）。这个过程从垃圾回收的&amp;quot;根对象&amp;quot;开始，只扫描到能够和&amp;quot;根对象&amp;quot;直接关联的对象，并作标记。所以这个过程虽然暂停了整个 JVM，但是很快就完成了。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;并发标记&lt;/strong&gt; ：这个阶段紧随初始标记阶段，在初始标记的基础上继续向下追溯标记。并发标记阶段，应用程序的线程和并发标记的线程并发执行，所以用户不会感受到停顿。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;并发预清理&lt;/strong&gt; ：并发预清理阶段仍然是并发的。在这个阶段，虚拟机查找在执行并发标记阶段新进入老年代的对象（可能会有一些对象从新生代晋升到老年代， 或者有一些对象被分配到老年代）。通过重新扫描，减少下一个阶段&amp;quot;重新标记&amp;quot;的工作，因为下一个阶段会 Stop The World。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;重新标记&lt;/strong&gt; ：这个阶段会暂停虚拟机，收集器线程扫描在 CMS 堆中剩余的对象。扫描从&amp;quot;跟对象&amp;quot;开始向下追溯，并处理对象关联。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;并发清理&lt;/strong&gt; ：清理垃圾对象，这个阶段收集器线程和应用程序线程并发执行。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;并发重置&lt;/strong&gt; ：这个阶段，重置 CMS 收集器的数据结构，等待下一次垃圾回收。&lt;/p&gt; &lt;p&gt;CMS 使得在整个收集的过程中只是很短的暂停应用的执行，可通过在 JVM 参数中设置 -XX:UseConcMarkSweepGC 来使用此收集器，不过此收集器仅用于 old 和 Perm（永生）的对象收集。CMS 减少了 stop the world 的次数，不可避免地让整体 GC 的时间拉长了。&lt;/p&gt; &lt;p&gt;Full GC 的次数说的是 stop the world 的次数，所以一次 CMS 至少会让 Full GC 的次数+2，因为 CMS Initial mark 和 remark 都会 stop the world，记做2次。而 CMS 可能失败再引发一次 Full GC。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/sabas5mskkifnquuql8lor4i1i.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;上图为线上某应用在进行 CMS GC 状态下的 GC 日志截图。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;2017-11-02T09:27:03.989+0800: 558115.552: [GC [1 CMS-initial-mark: 1774783K(1926784K)] 1799438K(2068800K), 0.0123430 secs] [Times: user=0.01 sys=0.01, real=0.02 secs]  2017-11-02T09:27:04.001+0800: 558115.565: [CMS-concurrent-mark-start] 2017-11-02T09:27:04.714+0800: 558116.277: [CMS-concurrent-mark: 0.713/0.713 secs] [Times: user=1.02 sys=0.03, real=0.71 secs]  2017-11-02T09:27:04.714+0800: 558116.277: [CMS-concurrent-preclean-start] 2017-11-02T09:27:04.722+0800: 558116.285: [CMS-concurrent-preclean: 0.008/0.008 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]  2017-11-02T09:27:04.722+0800: 558116.286: [CMS-concurrent-abortable-preclean-start] 2017-11-02T09:27:04.836+0800: 558116.399: [GC2017-11-02T09:27:04.836+0800: 558116.400: [ParNew: 138301K-&amp;gt;6543K(142016K), 0.0155540 secs] 1913085K-&amp;gt;1781327K(2068800K), 0.0160610 secs] [Times: user=0.03 sys=0.01, real=0.02 secs]  2017-11-02T09:27:05.005+0800: 558116.569: [CMS-concurrent-abortable-preclean: 0.164/0.283 secs] [Times: user=0.46 sys=0.02, real=0.28 secs]  2017-11-02T09:27:05.006+0800: 558116.570: [GC[YG occupancy: 72266 K (142016 K)]2017-11-02T09:27:05.006+0800: 558116.570: [Rescan (parallel) , 0.2523940 secs]2017-11-02T09:27:05.259+0800: 558116.822: [weak refs processing, 0.0011240 secs]2017-11-02T09:27:05.260+0800: 558116.823: [scrub string table, 0.0028570 secs] [1 CMS-remark: 1774783K(1926784K)] 1847049K(2068800K), 0.2566410 secs] [Times: user=0.14 sys=0.00, real=0.26 secs]  2017-11-02T09:27:05.265+0800: 558116.829: [CMS-concurrent-sweep-start] 2017-11-02T09:27:05.422+0800: 558116.986: [GC2017-11-02T09:27:05.423+0800: 558116.986: [ParNew: 120207K-&amp;gt;2740K(142016K), 0.0179330 secs] 1885446K-&amp;gt;1767979K(2068800K), 0.0183340 secs] [Times: user=0.03 sys=0.01, real=0.02 secs]  2017-11-02T09:27:06.240+0800: 558117.804: [GC2017-11-02T09:27:06.240+0800: 558117.804: [ParNew: 116404K-&amp;gt;3657K(142016K), 0.0134680 secs] 1286444K-&amp;gt;1173697K(2068800K), 0.0138460 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]  2017-11-02T09:27:06.966+0800: 558118.530: [GC2017-11-02T09:27:06.966+0800: 558118.530: [ParNew: 117321K-&amp;gt;2242K(142016K), 0.0135210 secs] 738838K-&amp;gt;623759K(2068800K), 0.0140130 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]  2017-11-02T09:27:07.144+0800: 558118.708: [CMS-concurrent-sweep: 1.820/1.879 secs] [Times: user=2.88 sys=0.14, real=1.88 secs]  2017-11-02T09:27:07.144+0800: 558118.708: [CMS-concurrent-reset-start] 2017-11-02T09:27:07.149+0800: 558118.713: [CMS-concurrent-reset: 0.005/0.005 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果你已掌握 CMS 的垃圾收集过程，那么上面的 GC 日志你应该很容易就能看的懂，这里我就不详细展开解释说明了。&lt;/p&gt; &lt;p&gt;此外 CMS 进行垃圾回收时也有可能会发生失败的情况。&lt;/p&gt; &lt;p&gt;异常情况有：&lt;/p&gt; &lt;p&gt;伴随 prommotion failed，然后 Full GC：&lt;/p&gt; &lt;p&gt;[prommotion failed：存活区内存不足，对象进入老年代，而此时老年代也仍然没有内存容纳对象，将导致一次Full GC]&lt;/p&gt; &lt;p&gt;伴随 concurrent mode failed，然后 Full GC：&lt;/p&gt; &lt;p&gt;[concurrent mode failed：CMS回收速度慢，CMS完成前，老年代已被占满，将导致一次Full GC]&lt;/p&gt; &lt;p&gt;频繁 CMS GC：&lt;/p&gt; &lt;p&gt;[内存吃紧，老年代长时间处于较满的状态]&lt;/p&gt; &lt;h3&gt;业务日志&lt;/h3&gt; &lt;p&gt;业务日志除了关注系统异常与业务异常之外，还要关注服务执行耗时情况，耗时过长的服务调用如果没有熔断等机制，很容易导致应用性能下降或服务不可用，服务不可用很容易导致雪崩。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/vhf3sfhckmhglo5lqov1scra04.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;上面是某一接口的调用情况，虽然大部分调用没有发生异常，但是执行耗时相对比较长。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;grep '[0-9]{3,}ms' *.log  找出调用耗时大于3位数的dao方法，把3改成4就是大于4位数 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;互联网应用目前几乎采用分布式架构，但不限于服务框架、消息中间件、分布式缓存、分布式存储等等。那么这些应用日志如何聚合起来进行分析呢? 首先，你需要一套分布式链路调用跟踪系统，通过在系统线程上线文间透传 traceId 和 rpcId，将所有日志进行聚合，例如淘宝的鹰眼，spring cloud zipkin等等。&lt;/p&gt; &lt;h2&gt;案列分析&lt;/h2&gt; &lt;h3&gt;CPU 使用率高问题定位&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/rb3m63d10gg8mpqb69flkjguji.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;按照2.1定位流程首先排除了系统层面的问题。&lt;/p&gt; &lt;p&gt;利用 top -Hp 6814 输出进程 ID 为 6814 的所有线程 CPU 使用率情况，发现某个线程使用率比较高，有些异常。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;printf '%x\n' 2304 #输出线程ID的16进制 jstack pid | grep '0x900' -C 30 --color    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出的日志表明该线程一直处于与 mysql I/O 状态：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;quot;Thread-6563&amp;quot; daemon prio=10 tid=0x00007fda419a9000 nid=0x900 runnable [0x00007fda2b2b1000]    java.lang.Thread.State: RUNNABLE         at java.net.SocketInputStream.socketRead0(Native Method)         at java.net.SocketInputStream.read(SocketInputStream.java:152)         at java.net.SocketInputStream.read(SocketInputStream.java:122)         at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)         at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)         at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)         - locked &amp;lt;0x00000007d03e81d0&amp;gt; (a com.mysql.jdbc.util.ReadAheadInputStream)         at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3116)         at com.mysql.jdbc.MysqlIO.nextRowFast(MysqlIO.java:2224)         at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:1999)         at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:3507)         at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:490)         at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:3198)         at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:2366)         at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2789)         at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2818)         - locked &amp;lt;0x00000007ddcc84e8&amp;gt; (a com.mysql.jdbc.JDBC4Connection)         at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2157)         - locked &amp;lt;0x00000007ddcc84e8&amp;gt; (a com.mysql.jdbc.JDBC4Connection)         at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1379)         - locked &amp;lt;0x00000007ddcc84e8&amp;gt; (a com.mysql.jdbc.JDBC4Connection)         at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2931)         at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)         at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2929)         at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)         at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2929)         at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:131)         at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:493)         at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:56)         at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:70)         at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:57)         at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:259)         at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:132)         at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:105)         at cn.com.company.xqy.framework.daoframework.interceptor.PanDaoInterceptor.interceptCachePage(PanDaoInterceptor.java:172)         at cn.com.company.xqy.framework.daoframework.interceptor.PanDaoInterceptor.intercept(PanDaoInterceptor.java:45)         at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:57)         at com.sun.proxy.$Proxy121.query(Unknown Source)         at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104)         at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)         at cn.com.company.xqy.framework.daoframework.PanSqlSessionTemplate$1.doInSqlSession(PanSqlSessionTemplate.java:111)         at cn.com.company.xqy.framework.daoframework.PanSqlSessionTemplate.executeWith(PanSqlSessionTemplate.java:454)         at cn.com.company.xqy.framework.daoframework.PanSqlSessionTemplate.selectList(PanSqlSessionTemplate.java:150)         at cn.com.company.xqy.framework.daoframework.PanSqlSessionTemplate.selectList(PanSqlSessionTemplate.java:93)         at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:114)         at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:58)         at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:43)         at com.sun.proxy.$Proxy45.select(Unknown Source)         at sun.reflect.GeneratedMethodAccessor4958.invoke(Unknown Source)         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)         at java.lang.reflect.Method.invoke(Method.java:606)         at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)         at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)         at cn.com.company.xqy.framework.log.interceptor.DalDigestLogInterceptor.invoke(DalDigestLogInterceptor.java:28)         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)         at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)         at com.sun.proxy.$Proxy46.select(Unknown Source)         at cn.com.company.xqy.finance.service.CustomerAccountTitleService.selectAllEnablesNoCache(CustomerAccountTitleService.java:1124)         at cn.com.company.xqy.finance.service.CustomerAccountTitleService$$FastClassBySpringCGLIB$$363dc6fc.invoke(&amp;lt;generated&amp;gt;)         at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)         at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)         at cn.com.company    .xqy.finance.service.CustomerAccountTitleService$$EnhancerBySpringCGLIB$$613b9107.selectAllEnablesNoCache(&amp;lt;generated&amp;gt;)         at cn.com.company.xqy.finance.service.datafix.BalanceFixService.amountStatistics(BalanceFixService.java:1121)         at cn.com.company.xqy.finance.service.datafix.BalanceFixService.scan(BalanceFixService.java:346)         at cn.com.company.xqy.finance.service.datafix.BalanceFixService.access$100(BalanceFixService.java:38)         at cn.com.company.xqy.finance.service.datafix.BalanceFixService$1.run(BalanceFixService.java:107)         at java.lang.Thread.run(Thread.java:745) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;利用 &lt;strong&gt;jmap -dump:format=b,file=/usr/local/logs/gc/dump.hprof {pid}&lt;/strong&gt; 以二进制输出档当前内存的堆情况，然后可以导入 MAT 等工具进行分析。如下图所示，点击 MAT 的支配树可以发现存在某个超大对象数组，实例对象数目多大30多万个。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/4c9jbte638ik0ppjgbnnkhesj3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;经过分析发现数组中每一个对象都是核心业务对象，我们的业务系统有一个定时任务线程会访问数据库某张业务表中的所有记录，然后加载至内存然后进行处理因此内存吃紧，导致 CPU 突然飙升。发现该问题后，已对该方案进行重新设计。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 31 Aug 2019 15:08:00 GMT</pubDate>
    </item>
    <item>
      <title>整理了一些 IDEA 中比较骚的技巧</title>
      <link>https://maruifu.cn/article/117</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;IDEA 有个很牛逼的功能，那就是后缀补全（不是自动补全），很多人竟然不知道这个操作，还在手动敲代码。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;这个功能可以使用代码补全来模板式地补全语句，如遍历循环语句（for、foreach）、使用 String.format() 包裹一个字符串、使用类型转化包裹一个表达式、根据判（非）空或者其它判别语句生成 if 语句、用 instanceOf 生成分支判断语句等。&lt;/p&gt; &lt;p&gt;使用的方式也很简单，就是在一个表达式后按下点号 . ，然后输入一些提示或者在列表中选择一个候选项，常见的候选项下面会给出 GIF 演示。&lt;/p&gt; &lt;h3&gt;fori 带索引的遍历&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/4bcbpcejpmgkapbk7hg5bs30b8.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;for 遍历&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/349h2n93pegc7pk6toeamk1qfh.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;if 条件判断&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/s0jrsash8oj53qb4j2s8uah3ke.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;cast 强转&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/v2v7plkmc8i7oqb6gotkl6ak5j.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;nn 判非空&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/u4sdshuvr6i90p3au7pb2m8ab6.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;notnull 判非空&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/7nvr245g3gjmrp1hksfpj09kn5.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;not 取反&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/38b4ntnidsirdqnpmiloqvfcij.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;null判空&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/tq6ibm8vfug3cr1alb1g8gvgtj.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;return 返回值&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/3h793k69mmgkhrt9uf38gslps7.gif" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;var声明&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/08/2ppoa7ro2mgrmprdqmt1e0vatj.gif" alt="alt" title="alt" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 25 Aug 2019 05:18:10 GMT</pubDate>
    </item>
    <item>
      <title>SQL如何实现MYSQL的递归查询，SQL实现MYSQL递归</title>
      <link>https://maruifu.cn/article/113</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;有时候经常需要迭代查询一些数据,比如按钮菜单,裂变。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;所周知，目前的mysql版本中并不支持直接的递归查询，但是通过递归到迭代转化的思路，还是可以在一句SQL内实现树的递归查询的。这个得益于Mysql允许在SQL语句内使用@变量。以下是示例代码。&lt;/p&gt; &lt;h2&gt;创建表格&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;CREATE TABLE `treenodes` (   `id` int , -- 节点ID   `nodename` varchar (60), -- 节点名称   `pid` int -- 节点父ID );  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;插入测试数据&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;INSERT INTO `treenodes` (`id`, `nodename`, `pid`) VALUES ('1','A','0'),('2','B','1'),('3','C','1'), ('4','D','2'),('5','E','2'),('6','F','3'), ('7','G','6'),('8','H','0'),('9','I','8'), ('10','J','8'),('11','K','8'),('12','L','9'), ('13','M','9'),('14','N','12'),('15','O','12'), ('16','P','15'),('17','Q','15'),('18','R','3'), ('19','S','2'),('20','T','6'),('21','U','8');  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;查询语句&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;  SELECT id AS ID,pid AS 父ID ,levels AS 父到子之间级数, paths AS 父到子路径 FROM (    SELECT id,pid,    @le:= IF (pid = 0 ,0,       IF( LOCATE( CONCAT('|',pid,':'),@pathlevel)  &amp;gt; 0 ,             SUBSTRING_INDEX( SUBSTRING_INDEX(@pathlevel,CONCAT('|',pid,':'),-1),'|',1) +1     ,@le+1) ) levels    , @pathlevel:= CONCAT(@pathlevel,'|',id,':', @le ,'|') pathlevel    , @pathnodes:= IF( pid =0,',0',        CONCAT_WS(',',       IF( LOCATE( CONCAT('|',pid,':'),@pathall) &amp;gt; 0 ,          SUBSTRING_INDEX( SUBSTRING_INDEX(@pathall,CONCAT('|',pid,':'),-1),'|',1)        ,@pathnodes ) ,pid ) )paths   ,@pathall:=CONCAT(@pathall,'|',id,':', @pathnodes ,'|') pathall      FROM treenodes,    (SELECT @le:=0,@pathlevel:='', @pathall:='',@pathnodes:='') vv   ORDER BY pid,id   ) src ORDER BY id  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;结果&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;    ID   父ID    父到子之间级数  父到子路径 ------    ------     --------------------    -------------------       1       0              0                     ,0                   2       1              1                     ,0,1                 3       1              1                     ,0,1                 4       2              2                     ,0,1,2               5       2              2                     ,0,1,2               6       3              2                     ,0,1,3               7       6              3                     ,0,1,3,6             8       0              0                     ,0                   9       8              1                     ,0,8                10       8             1                     ,0,8                11       8             1                     ,0,8                12       9             2                     ,0,8,9              13       9             2                     ,0,8,9              14      12            3                      ,0,8,9,12           15      12            3                     ,0,8,9,12           16      15            4                     ,0,8,9,12,15        17      15            4                     ,0,8,9,12,15        18       3            2                      ,0,1,3              19       2            2                      ,0,1,2              20       6            3                      ,0,1,3,6            21       8            1                      ,0,8       &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上就是一句SQL实现MYSQL的递归查询的实现全过程，希望对大家的学习有所帮助。&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 25 Aug 2019 04:53:00 GMT</pubDate>
    </item>
    <item>
      <title>github下载代码的速度太慢</title>
      <link>https://maruifu.cn/article/111</link>
      <content:encoded>&lt;p&gt;作为程序员，最大的同性交友网站估计是大家的标配了，常常会苦恼于git clone某个项目的时候速度太慢，看着控制台那几K十一二K的速度，吐血！！&lt;/p&gt; &lt;p&gt;原因很简单：github的CDN被高高的墙屏蔽所致了。 所以解决方案也很简单，就是手动把 cdn 和IP地址绑定一下。&lt;/p&gt; &lt;h2&gt;1、获取github地址&lt;/h2&gt; &lt;p&gt;访问 &lt;a href="http://github.com.ipaddress.com/" title="地址" target="_blank"&gt;http://github.com.ipaddress.com/&lt;/a&gt; 获取cdn域名以及ip地址&lt;/p&gt; &lt;h2&gt;2、获取 global.ssl.fastly地址&lt;/h2&gt; &lt;p&gt;&lt;a href="http://github.global.ssl.fastly.net.ipaddress.com/" title="github.global.ssl.fastly.net的IP地址" target="_blank"&gt;http://github.global.ssl.fastly.net.ipaddress.com/&lt;/a&gt; 获取cdn域名以及ip地址&lt;/p&gt; &lt;h2&gt;3、打开hosts映射&lt;/h2&gt; &lt;h3&gt;Windows环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;C:\Windows\System32\drivers\etc\hosts &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最末尾添加两句话保存:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;151.101.185.194 http://github.global.ssl.fastly.net  192.30.253.112 http://github.com &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;打开CMD刷新一下DNS就好了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ipconfig /flushdns &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;Linux环境&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;sudo gedit /etc/hosts &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;添加&lt;/p&gt; &lt;pre&gt;&lt;code&gt;192.30.253.112 http://github.com 151.101.185.194 http://github.global.ssl.fastly.net  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;保存,退出,并重启网络&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/etc/init.d/networking restart &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;速度对比:&lt;/h2&gt; &lt;p&gt;配置前&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Receiving objects:  17% (151/883), 348.00 KiB | 18.00 KiB/s &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;配置后&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Receiving objects:  81% (86141/104384), 81.31Mib | 562.00 KiB/s &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 23 Apr 2019 14:35:10 GMT</pubDate>
    </item>
    <item>
      <title>上传本地代码到github</title>
      <link>https://maruifu.cn/article/106</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;全球最大同性交友网站GitHub,你还不赶快加入进来!&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;第一步：下载Git&lt;/h2&gt; &lt;p&gt;下载地址: &lt;a href="https://gitforwindows.org/" title="点击我下载" target="_blank"&gt;https://gitforwindows.org/&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;第二步：建立git仓库&lt;/h2&gt; &lt;p&gt;Git bash Here 命令下 cd到你的本地项目根目录下，执行git命令&lt;/p&gt; &lt;pre&gt;&lt;code&gt;git init &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;第三步：将项目的所有文件添加到仓库中&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git add . &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;(如果想添加某个特定的文件，只需把.换成特定的文件名即可)&lt;/p&gt; &lt;h2&gt;第三步：将add的文件commit到仓库&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git commit -m &amp;quot;注释语句&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;第四步：去github上创建自己的Repository&lt;/h2&gt; &lt;p&gt;创建页面如下图所示：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/0vovb4abiuiuvoc7ai5pn7cla4.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;点击下面的Create repository，就会进入到类似下面的一个页面，拿到创建的仓库的https地址，红框标示的就是&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/vf17142dkqiibotb367d9s8akt.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;h2&gt;第五步：重点来了，将本地的仓库关联到github上&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git remote add origin https://github.com/MaRuifu/Java8NewFeatures &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;后面的https链接地址换成你自己的仓库url地址，也就是上面红框中标出来的地址&lt;/p&gt; &lt;h2&gt;第六步：上传github之前，要先pull一下&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git pull origin master &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;第七步，也就是最后一步，上传代码到github远程仓库&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;git push -u origin master &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行完后，如果没有异常，等待执行完就上传成功了，中间可能会让你输入Username和Password，你只要输入github的账号和密码就行了&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/q9bgaj6nh4gj7q8pbr9dto17fu.jpg" alt="" title="" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 21 Apr 2019 16:01:15 GMT</pubDate>
    </item>
    <item>
      <title>最全的常用正则表达式大全——包括校验数字、字符、一些特殊的需求等等</title>
      <link>https://maruifu.cn/article/102</link>
      <content:encoded>&lt;h2&gt;一、校验数字的表达式&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; 1 数字：^[0-9]*$    2 n位的数字：^d{n}$    3 至少n位的数字：^d{n,}$    4 m-n位的数字：^d{m,n}$    5 零和非零开头的数字：^(0|[1-9][0-9]*)$    6 非零开头的最多带两位小数的数字：^([1-9][0-9]*)+(.[0-9]{1,2})?$    7 带1-2位小数的正数或负数：^(-)?d+(.d{1,2})?$    8 正数、负数、和小数：^(-|+)?d+(.d+)?$    9 有两位小数的正实数：^[0-9]+(.[0-9]{2})?$  10 有1~3位小数的正实数：^[0-9]+(.[0-9]{1,3})?$  11 非零的正整数：^[1-9]d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^+?[1-9][0-9]*$  12 非零的负整数：^-[1-9][]0-9&amp;quot;*$ 或 ^-[1-9]d*$  13 非负整数：^d+$ 或 ^[1-9]d*|0$  14 非正整数：^-[1-9]d*|0$ 或 ^((-d+)|(0+))$  15 非负浮点数：^d+(.d+)?$ 或 ^[1-9]d*.d*|0.d*[1-9]d*|0?.0+|0$  16 非正浮点数：^((-d+(.d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]d*.d*|0.d*[1-9]d*))|0?.0+|0$  17 正浮点数：^[1-9]d*.d*|0.d*[1-9]d*$ 或 ^(([0-9]+.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*.[0-9]+)|([0-9]*[1-9][0-9]*))$  18 负浮点数：^-([1-9]d*.d*|0.d*[1-9]d*)$ 或 ^(-(([0-9]+.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*.[0-9]+)|([0-9]*[1-9][0-9]*)))$  19 浮点数：^(-?d+)(.d+)?$ 或 ^-?([1-9]d*.d*|0.d*[1-9]d*|0?.0+|0)$ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;二、校验字符的表达式&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; 1 汉字：^[一-龥]{0,}$  ^[\\u4e00-\\u9fa5]{0,}$  2 英文和数字：^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$   3 长度为3-20的所有字符：^.{3,20}$   4 由26个英文字母组成的字符串：^[A-Za-z]+$   5 由26个大写英文字母组成的字符串：^[A-Z]+$   6 由26个小写英文字母组成的字符串：^[a-z]+$   7 由数字和26个英文字母组成的字符串：^[A-Za-z0-9]+$   8 由数字、26个英文字母或者下划线组成的字符串：^w+$ 或 ^w{3,20}$   9 中文、英文、数字包括下划线：^[一-龥A-Za-z0-9_]+$ 10 中文、英文、数字但不包括下划线等符号：^[一-龥A-Za-z0-9]+$ 或 ^[一-龥A-Za-z0-9]{2,20}$ 11 可以输入含有^%&amp;amp;',;=?$&amp;quot;等字符：[^%&amp;amp;',;=?$&amp;quot;]+ 12 禁止输入含有~的字符：[^~&amp;quot;]+ &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;三、特殊需求表达式&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;  1 Email地址：^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$    2 域名：[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?    3 InternetURL：[a-zA-z]+://[^s]* 或 ^http://([w-]+.)+[w-]+(/[w-./?%&amp;amp;=]*)?$    4 手机号码：^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])d{8}$    5 电话号码(&amp;quot;XXX-XXXXXXX&amp;quot;、&amp;quot;XXXX-XXXXXXXX&amp;quot;、&amp;quot;XXX-XXXXXXX&amp;quot;、&amp;quot;XXX-XXXXXXXX&amp;quot;、&amp;quot;XXXXXXX&amp;quot;和&amp;quot;XXXXXXXX)：^((d{3,4}-)|d{3.4}-)?d{7,8}$     6 国内电话号码(0511-4405222、021-87888822)：d{3}-d{8}|d{4}-d{7}    7 身份证号(15位、18位数字)：^d{15}|d{18}$    8 短身份证号码(数字、字母x结尾)：^([0-9]){7,18}(x|X)?$ 或 ^d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$   15位：^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$  18位：^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$    9 帐号是否合法(字母开头，允许5-16字节，允许字母数字下划线)：^[a-zA-Z][a-zA-Z0-9_]{4,15}$  10 密码(以字母开头，长度在6~18之间，只能包含字母、数字和下划线)：^[a-zA-Z]w{5,17}$  11 强密码(必须包含大小写字母和数字的组合，不能使用特殊字符，长度在8-10之间)：^(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$    12 日期格式：^d{4}-d{1,2}-d{1,2} “yyyy-mm-dd“ 格式的日期校验，已考虑平闰年。：^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$  13 一年的12个月(01～09和1～12)：^(0?[1-9]|1[0-2])$  14 一个月的31天(01～09和1～31)：^((0?[1-9])|((1|2)[0-9])|30|31)$   15 金额校验，精确到2位小数。：^[0-9]+(.[0-9]{2})?$  16.有四种钱的表示形式我们可以接受:&amp;quot;10000.00&amp;quot; 和 &amp;quot;10,000.00&amp;quot;, 和没有 &amp;quot;分&amp;quot; 的 &amp;quot;10000&amp;quot; 和 &amp;quot;10,000&amp;quot;：^[1-9][0-9]*$   17.这表示任意一个不以0开头的数字,但是,这也意味着一个字符&amp;quot;0&amp;quot;不通过,所以我们采用下面的形式：^(0|[1-9][0-9]*)$  18.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号：^(0|-?[1-9][0-9]*)$   19.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分：^[0-9]+(.[0-9]+)?$   20.必须说明的是,小数点后面至少应该有1位数,所以&amp;quot;10.&amp;quot;是不通过的,但是 &amp;quot;10&amp;quot; 和 &amp;quot;10.2&amp;quot; 是通过的：^[0-9]+(.[0-9]{2})?$   21.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样：^[0-9]+(.[0-9]{1,2})?$   22.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样：^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$  23. 1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须：^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$   24 备注：这就是最终结果了,别忘了&amp;quot;+&amp;quot;可以用&amp;quot;*&amp;quot;替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里  25 xml文件：^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$  26 中文字符的正则表达式：[一-龥]  27 双字节字符：[^-ÿ]    (包括汉字在内，可以用来计算字符串的长度(一个双字节字符长度计2，ASCII字符计1))  28 空白行的正则表达式：s* (可以用来删除空白行)  29 HTML标记的正则表达式：&amp;lt;(S*?)[^&amp;gt;]*&amp;gt;.*?&amp;lt;/&amp;gt;|&amp;lt;.*? /&amp;gt;    (网上流传的版本太糟糕，上面这个也仅仅能部分，对于复杂的嵌套标记依旧无能为力)  30 首尾空白字符的正则表达式：^s*|s*$或(^s*)|(s*$)    (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)，非常有用的表达式)  31 腾讯QQ号：[1-9][0-9]{4,}    (腾讯QQ号从10000开始)  32 中国邮政编码：[1-9]d{5}(?!d)    (中国邮政编码为6位数字)  33 IP地址：d+.d+.d+.d+    (提取IP地址时有用)  34 IP地址：((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))      35 匹配HTML标签通过下面的表达式可以匹配出HTML中的标签属性。：&amp;lt;\\/?\\w+((\\s+\\w+(\\s*=\\s*(?:&amp;quot;.*?&amp;quot;|'.*?'|[\\^'&amp;quot;&amp;gt;\\s]+))?)+\\s*|\\s*)\\/?&amp;gt;  36 抽取注释 如果你需要移除HMTL中的注释，可以使用如下的表达式。：&amp;lt;!--(.*?)--&amp;gt;   37 查找CSS属性 通过下面的表达式，可以搜索到相匹配的CSS属性。：^\\s*[a-zA-Z\\-]+\\s*[:]{1}\\s[a-zA-Z0-9\\s.#]+[;]{1}  38 提取页面超链接 提取html中的超链接： (&amp;lt;a\\s*(?!.*\\brel=)[^&amp;gt;]*)(href=&amp;quot;https?:\\/\\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^&amp;quot;]+)&amp;quot;((?!.*\\brel=)[^&amp;gt;]*)(?:[^&amp;gt;]*)&amp;gt;  39 提取网页图片假若你想提取网页中所有图片信息，可以利用下面的表达式。：\\&amp;lt; *[img][^\\\\&amp;gt;]*[src] *= *[\\&amp;quot;\\']{0,1}([^\\&amp;quot;\\'\\ &amp;gt;]*)  40 提取Color Hex Codes有时需要抽取网页中的颜色代码，可以使用下面的表达式。：^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$  41 文件路径及扩展名校验 验证windows下文件路径和扩展名（下面的例子中为.txt文件）：^([a-zA-Z]\\:|\\\\)\\\\([^\\\\]+\\\\)*[^\\/:*?&amp;quot;&amp;lt;&amp;gt;|]+\\.txt(l)?$  42 提取URL链接 下面的这个表达式可以筛选出一段文本中的URL。：^(f|ht){1}(tp|tps):\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&amp;amp;=]*)?  43 检查URL的前缀 应用开发中很多时候需要区分请求是HTTPS还是HTTP，通过下面的表达式可以取出一个url的前缀然后再逻辑判断。：if (!s.match(/^[a-zA-Z]+:\\/\\//)){ s = 'http://' + s;}  44 校验IPv6地址 ：(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))  45 校验IP-v4地址：\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b  46 判断IE的版本：^.*MSIE [5-8](?:\\.[0-9]+)?(?!.*Trident\\/[5-9]\\.0).*$  47 密码的强度必须是包含大小写字母和数字的组合，不能使用特殊字符，长度在8-10之间。：^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$    &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 20 Apr 2019 20:40:00 GMT</pubDate>
    </item>
    <item>
      <title>人为什么要去旅行？这是我见过的最美的答案</title>
      <link>https://maruifu.cn/article/101</link>
      <content:encoded>&lt;p&gt;人为什么要去旅行？这是我见过的最美的答案&lt;/p&gt; &lt;p&gt;看到朋友圈的谁谁谁，用两句鸡汤文配上几张美图，再加一个定位，我们默默的评论一句“你又出去玩儿啦，好羡慕”，然后手指继续向下滑动，看着其他人的生活，好像都那么多姿多彩。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/r1kdpb4hs2h2erginc69umnpgm.gif" alt="]]]1"&gt;&lt;/p&gt; &lt;p&gt;唯独自己，公司--家，两点一线，枯燥乏味，日复一日。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/u3caf3bd1ii40r0en6i1j8l86r.gif" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;你羡慕的生活，你羡慕的人，你做不到的事情，有人做到了。你不禁疑问，他们哪里来的那么多的时间和金钱？旅行，又究竟是为了什么？&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/6po4oo574cjberk7dstauajtgq.gif" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;01&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行，可以滋养我们在一个已经待腻的城市里，日渐枯萎的心。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/0e20kvcp10ip4qtqu9jnkd24pk.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;我们花那么多钱买机票订酒店，花那么多时间查攻略，不是为了简单的发发朋友圈晒几张世界大同的照片，而是用这些时间和钱让漫漫人生长河，变得有趣。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/6pm3m4il88jv0o7kafd1vvhq0d.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;毕竟这世界上，好看的人很多，有趣的灵魂却很少。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/q2rroavqrai8fqrba3km4kp5sg.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;02&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行，是看清世界的一个最直接的途径。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/v3rse75340gtto6p2d9bk1bfga.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;电影，告诉我世界有无限可能，旅行，让我可以真切的感受世界。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/r9lvcttdqmie7p4qb16i693qfm.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;不要只把向往的远方放在梦想里，而是放在你的计划里。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/4s3if33ti0igpq329hq8tnmckc.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;03&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行，是一种热爱，而我们愿为了这份热爱买单。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/4nt8l53rloh6ao8s4qm04q1iq7.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;因为心中有那份狂热，你会想要花时间、花金钱，去为了远方而行动。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/027358tp48ifnp1168pvvurrds.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;远方像是一种执念，更像是一种瘾，你不去，它便呼唤你，召唤你，直到你寻踪而至。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/pm91jigh7aiuvo8qe2e3a5oka3.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;04&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为有些事，现在不做，一辈子都不会再做了。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/ue71j31ppujitr6cvukor969q6.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;该庆幸的是年岁还轻，时光未老，眼还未浊，腿还能动。有着无限的精力和不竭的动力为想要的旅行奔波。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/vqsdhsa69ih5dqjf6900l2l04j.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;我不畏颠沛流离，只为看你一眼。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/3b6vn3d632gbfqlm5qmmiarcru.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;05&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行，是一种冲动。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/2ajp48ak0mjhornmm111o2805s.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;你不可能等攒够了钱，再去过想要的生活，那个时候，可能世界都变化了一圈。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/1nf9a01vr0h1qq417omt7a2q2j.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;所以，想去哪，就用自己的方式去看看这个世界，无论是徒步、搭车、穷游，还是自驾，都能够有不一样的惊喜。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/opuh3v8shqgquoekvn4j102hi0.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;06&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行的脚步，永远是向前的。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/6mhcgi76rsin6p8didb9fb1jig.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;停在原地的理由很多，但我们总要坚定一个出发的理由。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/qmqe3rek6cggppav8bfb3qf2i9.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;07&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行可以更好的工作和生活。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/4j8v2q8ti2hkpo6svhj9e2p0b2.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;即使知道旅行过后，还是要回到原来的城市，工作、生活，但却有一种期待，为了下一次的远行，更好的生活。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/s0vqk6b6e8hdfpb95pouel2qmj.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;见到了世界的生活方式，才能更加清楚的看到内心想要的生活。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/7l11q1gossjopo44o8pegbodfo.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;center&gt; &lt;div style="width:30px;height:30px;border:2px solid red;border-radius:50px;line-height:20px;color:red;"&gt;08&lt;/div&gt; &lt;/center&gt; &lt;p&gt;因为旅行，是年老时，回忆的资本。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/qs6bomo1doi1co4bvkgs0snq20.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;不想年老之时，回想一生，除了千篇一律的工作和平淡入水的生活，别无他想。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/6n4lnktftghaeqig61j3h3p3o7.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;即使老了，也想要做一个积极乐观的老太太，一个时髦超前的老太太。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/2r59j9sgmkg30oigkqt4m887je.jpg" alt="]]]"&gt;&lt;/p&gt; &lt;p&gt;我们花那么多时间和金钱去旅行，是为了我们那么平凡，却又可以不凡。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 20 Apr 2019 20:11:33 GMT</pubDate>
    </item>
    <item>
      <title>一种绝对提高开发水平的方法</title>
      <link>https://maruifu.cn/article/95</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;一种绝对提高开发水平的方法,哈哈记单词!!&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;常用单词&lt;/h2&gt; &lt;h3&gt;初级部分&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;第一节  void：[vɔid] 空的 main：[mein] 主要的 class：[klɑ:s] 类 system：['sistəm] 系统 out：[aut] 出，外，输出 print：[print ] 打印 public['pʌblik] 公共的,公用的 static['stætik] 静的;静态的;静止的 oracle: [ˈɔ:rəkl] 甲骨文公司 eclipse：[i'klips] java编程软件    第二节   int：[int] 整型 char：[tʃɑ:] 字符型 scanner：['skænə] 接收输入，扫描器 integer：['intidʒə]整数 整型 type：[taip]类型 string：[striŋ] 字符串类型 double：['dʌbl] 双精度浮点型  第三节  boolean：['bu🇱🇮ən] 布尔类型真假二值 true：[tru:]真 false：[fɔ:ls]假 不正确的 if：[if] 如果 else：[els] 否则 break：[brek] 打破，跳出  第四节  case：[keis] 情况,实例 default：[di'fɔ:lt] 默认 switch：[switʃ] 开关，切换 break：[breik] 退出 match：[mætʃ] 匹配 exception：[ik'sepʃən] 异常 equals：['i:kwəls] 相等 第五节  while：[hwail] 当什么时候，常作循环 index：['indeks] 索引 bug：[bʌg] 缺陷 debug：[di:'bʌg] 调试 step：[step] 步骤 error：['erə] 错误 answer：['ɑ:nsə] 答案 回答  第六节  rate：[reit] 比率 young：[jʌŋ] 年轻的 customer：['kʌstəmə] 顾客 买主 birthday：['bə:θdei] 生日 point：[pɔint] 分数 得分 点 continue：[kən'tinju:] 继续、进入到下一个循环 return：[ri'tə:n] 返回 total：['təutl] 合计、总人数,,全体的  第七节  array：[ə'rei] 数组 length：[leŋθ] 长度 sort：[sɔ:t] 分组 排序 primitive：['primitiv] 初始的 简单的 reference：['refərəns] 参照 证明 关系 info：['infəu] 通知 报告 消息 interface：['intəfeis] 接口  第八节  random：['rændəm] 随机 insert：[in'sə:t] 插入 嵌入 compare：[kəm'pɛə] 比较 对照 ignore：[ig'nɔ:] 忽视 不理会 invert：[in'və:t] 使转位 倒转 password：['pɑ:swə:d] 密码 口令  第九节  bit：[bɪt] 位,0或1 byte:[baɪt] 字节，8个bit put:[pʊt] 放 log:[lɔ:g] 日志 show:[ʃoʊ] 展示，显示 第十节  change：[tʃeindʒ] 交换 互换 administrator：[əd'ministreitə] 管理员 initial：[i'niʃəl] 开始的、最初的 class：[klɑ:s] 类 object：['ɔbdʒikt] 物体 对象 encapsulation：[in,kæpsju'leiʃən] 封装 null：[nʌl] 空的  第十一节  person：['pə:sn] 人 start：[stɑ:t] 开始 menu：['menju:] 菜单 login：[lɔg'in] 登陆 main：[mein] 主要的 document：['dɔkjumənt] 文档 display：[di'splei] 显示 method：['meθəd] 方法 条理 version：['və:ʃən] 版本 parameter：[pə'ræmitɚ] 参数 since：[sins] 自…..之后 calculator：['kælkju,leitə] 计算器  第十二节  shape：[ʃeip] 形状 open：[əup] 打开 date：[deit] 日期 operate：['ɔpə,reitə] 操作 protect：[prə'tekt] 保卫 护卫 private：['praivit] 私人的 私有的  第十三节  manage：['mænidʒ] 控制，管理 search：[sə:tʃ] 搜寻 查找 upper：['ʌpə] 上面的  第十四节  equal：['i:kwəl] 相等的 ignore：[ig'nɔ:] 忽视 驳回 lower：['ləuə] 较低的 下部的 last：[lɑ:st] 最后的 trim：[trim] 裁切 concatenate：[kɔn'kætineit] 使连锁 连成一串 使连接 buffer：['bʌfə] 缓冲 final：['fainl] 最后的 最终的  第十五节  定义变量中常用的单词 score：[skɔ:]成绩 price：[prais]价钱 test：[test]实验，测试 demo：['deməu]示例 sum：[sʌm] 和 num：[nʌm] 数字 height：[hait] 身高 weight ：[weit] 体重 music：['mju:zik] 音乐 computer：[kəm'pju:tə] 电脑 student：['stju:dənt] 学生 total：['təutl] 总计的,总括的,全体的  第十六节  max 最大的 min 最小的 avg 平均分 Add 加 Minus 减 multiply：['mʌltiplai] 乘 divide：[di'vaid] 除 Monday：['mʌndei] 星期一 Tuesday：['tju:zdi] 星期二 Wednesday：['wenzdi] 星期三 Thursday：['θə:zdi] 星期四 Friday：['fraidi] 星期五 Saturday：['sætədi] 星期六 Sunday：['sʌndi] 星期日 月份+缩写 一月：January Jan. 二月：February Feb. 三月：March Mar. 四月：April Apr. 五月：May – 六月：June – 七月：July – 八月：August Aug. 九月：September Sept. 十月：October Oct. 十一月：November Nov. 十二月：December Dec &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;高级部分&lt;/h3&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;类&lt;/th&gt;&lt;th&gt;频率&lt;/th&gt;&lt;th&gt;中文&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;class&lt;/td&gt;&lt;td&gt;23&lt;/td&gt;&lt;td&gt;类&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;operator&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;操作符&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;function&lt;/td&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;函数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;object&lt;/td&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;对象&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;exception&lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;异常&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;template&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;模板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;call&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;调用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;list&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;列表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;resolution&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;分辨率&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;copy&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;复制&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;declaration&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;声明&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;derived&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;派生的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;global&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;全局&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;14&lt;/td&gt;&lt;td&gt;hard&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;硬&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;base&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;基地&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;box&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;盒子&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;file&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;文件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;initialization&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;初始化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;library&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;库&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;member&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;成员&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;overloaded&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;重载&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;scope&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;23&lt;/td&gt;&lt;td&gt;binary&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;二进制&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;by&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;通过&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;25&lt;/td&gt;&lt;td&gt;forwarding&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;转发&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;generic&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;通用的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;27&lt;/td&gt;&lt;td&gt;group&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;集团&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;28&lt;/td&gt;&lt;td&gt;GUI&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;图形界面&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;if&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;如果&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;immediate&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;立即&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;31&lt;/td&gt;&lt;td&gt;infinite&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;无限&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;inline&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;内联&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;33&lt;/td&gt;&lt;td&gt;loop&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;循环&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;parameter&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;参数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;35&lt;/td&gt;&lt;td&gt;specialization&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;专业化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;条&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;bit&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;位&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;38&lt;/td&gt;&lt;td&gt;data&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;数据&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;39&lt;/td&gt;&lt;td&gt;database&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;数据库&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;40&lt;/td&gt;&lt;td&gt;dereference&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;间接引用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;41&lt;/td&gt;&lt;td&gt;disk&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;磁盘&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;42&lt;/td&gt;&lt;td&gt;equality&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;相等&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;43&lt;/td&gt;&lt;td&gt;explicit&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;显式的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;44&lt;/td&gt;&lt;td&gt;field&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;字段&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;45&lt;/td&gt;&lt;td&gt;framework&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;框架&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;46&lt;/td&gt;&lt;td&gt;hierarchy&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;层次结构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;47&lt;/td&gt;&lt;td&gt;memory&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;内存&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;48&lt;/td&gt;&lt;td&gt;message&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;消息&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;49&lt;/td&gt;&lt;td&gt;pass&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;通过&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;recursive&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;递归&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;51&lt;/td&gt;&lt;td&gt;return&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;返回&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;52&lt;/td&gt;&lt;td&gt;specification&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;规范&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;53&lt;/td&gt;&lt;td&gt;standard&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;标准&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;54&lt;/td&gt;&lt;td&gt;type&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;类型&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;55&lt;/td&gt;&lt;td&gt;algorithm&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;算法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;56&lt;/td&gt;&lt;td&gt;and&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;和&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;57&lt;/td&gt;&lt;td&gt;assembly&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;程序集、组装、装配、部 件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;58&lt;/td&gt;&lt;td&gt;assignment&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;赋值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;59&lt;/td&gt;&lt;td&gt;based&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;基于&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;60&lt;/td&gt;&lt;td&gt;binding&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;绑定&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;61&lt;/td&gt;&lt;td&gt;build&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;构建&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;62&lt;/td&gt;&lt;td&gt;button&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;按钮&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;63&lt;/td&gt;&lt;td&gt;check&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;检査&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;64&lt;/td&gt;&lt;td&gt;clause&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;条款&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;65&lt;/td&gt;&lt;td&gt;client&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;客户端&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;66&lt;/td&gt;&lt;td&gt;code&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;代码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;67&lt;/td&gt;&lt;td&gt;coded&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;编码&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;68&lt;/td&gt;&lt;td&gt;compatible&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;兼容的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;69&lt;/td&gt;&lt;td&gt;constant&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;常数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;70&lt;/td&gt;&lt;td&gt;container&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;容器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;71&lt;/td&gt;&lt;td&gt;deduction&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;扣除、演绎&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;72&lt;/td&gt;&lt;td&gt;definition&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;73&lt;/td&gt;&lt;td&gt;design&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;设计&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;74&lt;/td&gt;&lt;td&gt;dot&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;点&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;75&lt;/td&gt;&lt;td&gt;event&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;事件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;76&lt;/td&gt;&lt;td&gt;exit&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;退出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;77&lt;/td&gt;&lt;td&gt;expansion&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;扩张&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;78&lt;/td&gt;&lt;td&gt;export&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;导岀&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;79&lt;/td&gt;&lt;td&gt;expression&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;表达式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;80&lt;/td&gt;&lt;td&gt;facility&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;设施&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;81&lt;/td&gt;&lt;td&gt;feature&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;功能&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;82&lt;/td&gt;&lt;td&gt;firmware&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;固件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;83&lt;/td&gt;&lt;td&gt;flag&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;标记&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;84&lt;/td&gt;&lt;td&gt;flash&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;动画、闪光&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;85&lt;/td&gt;&lt;td&gt;flexibility&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;灵活性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;86&lt;/td&gt;&lt;td&gt;flush&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;清空&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;87&lt;/td&gt;&lt;td&gt;font&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;字体&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;88&lt;/td&gt;&lt;td&gt;for&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;为，循环&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;89&lt;/td&gt;&lt;td&gt;form&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;形式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;90&lt;/td&gt;&lt;td&gt;formal&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;正式的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;91&lt;/td&gt;&lt;td&gt;forward&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;向前&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;92&lt;/td&gt;&lt;td&gt;fractal&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;分形&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;93&lt;/td&gt;&lt;td&gt;full&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;完整的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;94&lt;/td&gt;&lt;td&gt;functionality&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;功能&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;95&lt;/td&gt;&lt;td&gt;functor&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;仿函数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;96&lt;/td&gt;&lt;td&gt;game&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;游戏&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;97&lt;/td&gt;&lt;td&gt;generate&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;生成&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;98&lt;/td&gt;&lt;td&gt;getter&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;获取器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;99&lt;/td&gt;&lt;td&gt;guard&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;保护，守卫&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;hand&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;手&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;101&lt;/td&gt;&lt;td&gt;handle&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;处理&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;102&lt;/td&gt;&lt;td&gt;handler&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;处理程序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;103&lt;/td&gt;&lt;td&gt;handling&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;处理&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;104&lt;/td&gt;&lt;td&gt;hardware&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;硬件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;105&lt;/td&gt;&lt;td&gt;hash&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;哈希&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;106&lt;/td&gt;&lt;td&gt;header&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;头&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;107&lt;/td&gt;&lt;td&gt;heap&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;堆&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;108&lt;/td&gt;&lt;td&gt;hook&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;钩子&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;109&lt;/td&gt;&lt;td&gt;hyperlink&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;超链接&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;110&lt;/td&gt;&lt;td&gt;icon&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;图标&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;111&lt;/td&gt;&lt;td&gt;IDE&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;集成开发环境&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;112&lt;/td&gt;&lt;td&gt;identifier&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;标识符&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;113&lt;/td&gt;&lt;td&gt;image&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;图像&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;immutability&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;不变性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;115&lt;/td&gt;&lt;td&gt;immutable&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;不可变的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;116&lt;/td&gt;&lt;td&gt;implement&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;实现&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;117&lt;/td&gt;&lt;td&gt;implementation&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;实现&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;118&lt;/td&gt;&lt;td&gt;implicit&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;隐式的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;119&lt;/td&gt;&lt;td&gt;inport&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;导入&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;120&lt;/td&gt;&lt;td&gt;increment&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;增量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;121&lt;/td&gt;&lt;td&gt;information&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;信息、&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;122&lt;/td&gt;&lt;td&gt;infrastructure&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;基础设施&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;123&lt;/td&gt;&lt;td&gt;inheritance&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;继承&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;124&lt;/td&gt;&lt;td&gt;initialize&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;初始化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;125&lt;/td&gt;&lt;td&gt;inner&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;内心的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;126&lt;/td&gt;&lt;td&gt;instance&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;实例&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;127&lt;/td&gt;&lt;td&gt;instantiated&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;实例化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;128&lt;/td&gt;&lt;td&gt;instantiation&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;实例化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;129&lt;/td&gt;&lt;td&gt;integer&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;整数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;130&lt;/td&gt;&lt;td&gt;integrate&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;集成&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;131&lt;/td&gt;&lt;td&gt;interacts&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;相互作用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;132&lt;/td&gt;&lt;td&gt;interface&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;接口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;133&lt;/td&gt;&lt;td&gt;interpreter&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;翻译&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;134&lt;/td&gt;&lt;td&gt;invariants&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;不变量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;135&lt;/td&gt;&lt;td&gt;invoke&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;调用&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;136&lt;/td&gt;&lt;td&gt;iterate&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;迭代&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;137&lt;/td&gt;&lt;td&gt;language&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;语言&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;138&lt;/td&gt;&lt;td&gt;level&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;水平&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;139&lt;/td&gt;&lt;td&gt;local&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;本地&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;140&lt;/td&gt;&lt;td&gt;lock&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;锁&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;141&lt;/td&gt;&lt;td&gt;modeling&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;建模&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;142&lt;/td&gt;&lt;td&gt;network&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;网络&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;143&lt;/td&gt;&lt;td&gt;number&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;数量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;144&lt;/td&gt;&lt;td&gt;only&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;只有&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;145&lt;/td&gt;&lt;td&gt;partial&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;部分&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;146&lt;/td&gt;&lt;td&gt;pattern&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;模式&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;147&lt;/td&gt;&lt;td&gt;pointer&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;指针&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;148&lt;/td&gt;&lt;td&gt;refer&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;请参考&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;149&lt;/td&gt;&lt;td&gt;reference&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;参考&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;150&lt;/td&gt;&lt;td&gt;runtime&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;运行时&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;151&lt;/td&gt;&lt;td&gt;search&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;搜索&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;152&lt;/td&gt;&lt;td&gt;server&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;服务器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;153&lt;/td&gt;&lt;td&gt;shaking&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;颤抖&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;154&lt;/td&gt;&lt;td&gt;specialization:&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;专门化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;155&lt;/td&gt;&lt;td&gt;stack&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;堆栈&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;156&lt;/td&gt;&lt;td&gt;table&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;157&lt;/td&gt;&lt;td&gt;value&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;价值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;158&lt;/td&gt;&lt;td&gt;access&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;访问&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;159&lt;/td&gt;&lt;td&gt;address&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;地址&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;160&lt;/td&gt;&lt;td&gt;Al&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;人工智能&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;161&lt;/td&gt;&lt;td&gt;application&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;应用程序&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;162&lt;/td&gt;&lt;td&gt;architecture&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;体系结构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;163&lt;/td&gt;&lt;td&gt;argument&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;论点&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;164&lt;/td&gt;&lt;td&gt;array&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;数组&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;165&lt;/td&gt;&lt;td&gt;arrow&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;箭头&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;166&lt;/td&gt;&lt;td&gt;assert&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;断言&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;167&lt;/td&gt;&lt;td&gt;assign&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;168&lt;/td&gt;&lt;td&gt;associated&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;相关的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;169&lt;/td&gt;&lt;td&gt;atomic&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;原子&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;170&lt;/td&gt;&lt;td&gt;attribute&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;属性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;171&lt;/td&gt;&lt;td&gt;audio&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;音频&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;172&lt;/td&gt;&lt;td&gt;background&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;背景&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;173&lt;/td&gt;&lt;td&gt;bandwidth&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;174&lt;/td&gt;&lt;td&gt;batch&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;批处理&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;175&lt;/td&gt;&lt;td&gt;benefit&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;利益&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;176&lt;/td&gt;&lt;td&gt;bitmap&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;位图&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;177&lt;/td&gt;&lt;td&gt;bitwise&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;位&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;178&lt;/td&gt;&lt;td&gt;block&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;块&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;179&lt;/td&gt;&lt;td&gt;body&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;身体&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;180&lt;/td&gt;&lt;td&gt;boolean&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;布尔&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;181&lt;/td&gt;&lt;td&gt;border&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;边境&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;182&lt;/td&gt;&lt;td&gt;brace&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;撐&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;183&lt;/td&gt;&lt;td&gt;bracket&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;支架&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;184&lt;/td&gt;&lt;td&gt;breakpoint&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;断点&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;185&lt;/td&gt;&lt;td&gt;bus&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;总线&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;186&lt;/td&gt;&lt;td&gt;business&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;业务&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;187&lt;/td&gt;&lt;td&gt;buttons&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;按钮&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;188&lt;/td&gt;&lt;td&gt;byte&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;字节&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;189&lt;/td&gt;&lt;td&gt;cache&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;缓存&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;190&lt;/td&gt;&lt;td&gt;callback&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;回调&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;191&lt;/td&gt;&lt;td&gt;candidate&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;候选&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;192&lt;/td&gt;&lt;td&gt;card&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;卡&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;193&lt;/td&gt;&lt;td&gt;chain&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;链&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;194&lt;/td&gt;&lt;td&gt;character&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;字符&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;195&lt;/td&gt;&lt;td&gt;checked&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;检查&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;196&lt;/td&gt;&lt;td&gt;child&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;子&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;197&lt;/td&gt;&lt;td&gt;cleanup&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;清理&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;198&lt;/td&gt;&lt;td&gt;clipboard&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;剪贴板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;199&lt;/td&gt;&lt;td&gt;clone&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;克隆&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;collection&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;集合&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;201&lt;/td&gt;&lt;td&gt;combo&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;组合&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;202&lt;/td&gt;&lt;td&gt;command&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;命令&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;203&lt;/td&gt;&lt;td&gt;common&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;常见的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;204&lt;/td&gt;&lt;td&gt;communication&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;沟通&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;205&lt;/td&gt;&lt;td&gt;compile&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;编译&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;206&lt;/td&gt;&lt;td&gt;compiler&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;编译器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;207&lt;/td&gt;&lt;td&gt;component&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;组件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;208&lt;/td&gt;&lt;td&gt;composition&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;作文&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;209&lt;/td&gt;&lt;td&gt;computer&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;电脑&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;210&lt;/td&gt;&lt;td&gt;computing&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;计算&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;211&lt;/td&gt;&lt;td&gt;concept&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;概念&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;212&lt;/td&gt;&lt;td&gt;concrete&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;具体&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;213&lt;/td&gt;&lt;td&gt;concurrent&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;并发&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;214&lt;/td&gt;&lt;td&gt;configuration&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;配置&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;215&lt;/td&gt;&lt;td&gt;connection&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;连接&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;216&lt;/td&gt;&lt;td&gt;console&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;控制台&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;217&lt;/td&gt;&lt;td&gt;const&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;常量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;218&lt;/td&gt;&lt;td&gt;constraint&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;约束&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;219&lt;/td&gt;&lt;td&gt;construct&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;构造&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;220&lt;/td&gt;&lt;td&gt;constructor&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;构造函数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;221&lt;/td&gt;&lt;td&gt;containment&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;容器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;222&lt;/td&gt;&lt;td&gt;context&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;上下文&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;223&lt;/td&gt;&lt;td&gt;contract&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;合同、契约&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;224&lt;/td&gt;&lt;td&gt;control&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;控制&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;225&lt;/td&gt;&lt;td&gt;cover&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;覆盖、封面&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;226&lt;/td&gt;&lt;td&gt;create&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;创建&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;227&lt;/td&gt;&lt;td&gt;creation&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;创建&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;228&lt;/td&gt;&lt;td&gt;cursor&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;光标&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;229&lt;/td&gt;&lt;td&gt;custom&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;自定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;230&lt;/td&gt;&lt;td&gt;datagram&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;数据报&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;231&lt;/td&gt;&lt;td&gt;dead&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;死&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;232&lt;/td&gt;&lt;td&gt;debug&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调试&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;233&lt;/td&gt;&lt;td&gt;debugger&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调试器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;234&lt;/td&gt;&lt;td&gt;default&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;默认的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;235&lt;/td&gt;&lt;td&gt;defer&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;推迟&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;236&lt;/td&gt;&lt;td&gt;define&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;定义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;237&lt;/td&gt;&lt;td&gt;delegate&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;委托&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;238&lt;/td&gt;&lt;td&gt;delegation&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;委托&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;239&lt;/td&gt;&lt;td&gt;demarshal&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;用于取消&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;240&lt;/td&gt;&lt;td&gt;derivation&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;推导&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;241&lt;/td&gt;&lt;td&gt;destroy&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;销毁&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;242&lt;/td&gt;&lt;td&gt;destructor&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;析构函数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;243&lt;/td&gt;&lt;td&gt;device&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;设备&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;244&lt;/td&gt;&lt;td&gt;dialog&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;对话框&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;245&lt;/td&gt;&lt;td&gt;direction&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;方向&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;246&lt;/td&gt;&lt;td&gt;directive&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;指令&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;247&lt;/td&gt;&lt;td&gt;directory&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;目录&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;248&lt;/td&gt;&lt;td&gt;dispatch&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调度&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;249&lt;/td&gt;&lt;td&gt;distributed&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;分:?^&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;250&lt;/td&gt;&lt;td&gt;document&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;文档&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;251&lt;/td&gt;&lt;td&gt;driven&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;驱动&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;252&lt;/td&gt;&lt;td&gt;driver&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;驱动器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;253&lt;/td&gt;&lt;td&gt;dynamic&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;动态&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;254&lt;/td&gt;&lt;td&gt;efficiency&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;效率&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;255&lt;/td&gt;&lt;td&gt;efficient&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;高效&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;256&lt;/td&gt;&lt;td&gt;encapsulation&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;封装&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;257&lt;/td&gt;&lt;td&gt;enclosing&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;封闭&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;258&lt;/td&gt;&lt;td&gt;end&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;结束&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;259&lt;/td&gt;&lt;td&gt;entity&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;实体&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;260&lt;/td&gt;&lt;td&gt;enum&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;枚举&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;261&lt;/td&gt;&lt;td&gt;enumerators&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;枚举成员&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;262&lt;/td&gt;&lt;td&gt;equal&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;平等的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;263&lt;/td&gt;&lt;td&gt;equivalence&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;等价&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;264&lt;/td&gt;&lt;td&gt;equivalent&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;等效&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;265&lt;/td&gt;&lt;td&gt;escape&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;逃避&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;266&lt;/td&gt;&lt;td&gt;evaluate&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;评估&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;267&lt;/td&gt;&lt;td&gt;head&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;头&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;268&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;高&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;269&lt;/td&gt;&lt;td&gt;in&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;在&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;270&lt;/td&gt;&lt;td&gt;item&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;顶&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;271&lt;/td&gt;&lt;td&gt;iteration&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;迭代&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;272&lt;/td&gt;&lt;td&gt;iterative&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;迭代&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;273&lt;/td&gt;&lt;td&gt;iterator&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;迭代器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;274&lt;/td&gt;&lt;td&gt;laser&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;激光&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;275&lt;/td&gt;&lt;td&gt;lifetime&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;一生&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;276&lt;/td&gt;&lt;td&gt;line&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;行、线&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;277&lt;/td&gt;&lt;td&gt;link&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;链接&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;278&lt;/td&gt;&lt;td&gt;linker&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;链接器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;279&lt;/td&gt;&lt;td&gt;literal&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;字面意义的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;280&lt;/td&gt;&lt;td&gt;load&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;负载&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;281&lt;/td&gt;&lt;td&gt;loader&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;加载器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;282&lt;/td&gt;&lt;td&gt;macro&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;宏&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;283&lt;/td&gt;&lt;td&gt;magic&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;魔法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;284&lt;/td&gt;&lt;td&gt;maintain&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;维护&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;285&lt;/td&gt;&lt;td&gt;manipulator&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;操纵器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;286&lt;/td&gt;&lt;td&gt;marshal&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;排列&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;287&lt;/td&gt;&lt;td&gt;mechanism&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;机制&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;288&lt;/td&gt;&lt;td&gt;memberwise&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;成员逐一&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;289&lt;/td&gt;&lt;td&gt;menu&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;菜单&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;290&lt;/td&gt;&lt;td&gt;meta&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;元&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;291&lt;/td&gt;&lt;td&gt;method&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;方法&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;292&lt;/td&gt;&lt;td&gt;micro&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;微&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;293&lt;/td&gt;&lt;td&gt;middleware&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;中间件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;294&lt;/td&gt;&lt;td&gt;model&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;模型&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;295&lt;/td&gt;&lt;td&gt;modem&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调制解调器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;296&lt;/td&gt;&lt;td&gt;modifier&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;修怖符&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;297&lt;/td&gt;&lt;td&gt;module&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;模块&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;298&lt;/td&gt;&lt;td&gt;most&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;大多数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;299&lt;/td&gt;&lt;td&gt;mouse&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;鼠标&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;300&lt;/td&gt;&lt;td&gt;multi&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;多&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;301&lt;/td&gt;&lt;td&gt;mutable&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;可变的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;302&lt;/td&gt;&lt;td&gt;namespace&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;命名空间&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;303&lt;/td&gt;&lt;td&gt;native&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;本机、本地&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;304&lt;/td&gt;&lt;td&gt;nested&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;嵌套的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;305&lt;/td&gt;&lt;td&gt;online&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;在线&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;306&lt;/td&gt;&lt;td&gt;opaque&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;不透明的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;307&lt;/td&gt;&lt;td&gt;operand&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;操作数&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;308&lt;/td&gt;&lt;td&gt;operate&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;操作&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;309&lt;/td&gt;&lt;td&gt;operating&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;操作&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;310&lt;/td&gt;&lt;td&gt;operation&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;操作&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;311&lt;/td&gt;&lt;td&gt;option&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;选顶&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;312&lt;/td&gt;&lt;td&gt;ordinary&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;普通的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;313&lt;/td&gt;&lt;td&gt;oriented&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;面向&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;314&lt;/td&gt;&lt;td&gt;overflow&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;溢出&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;315&lt;/td&gt;&lt;td&gt;overhead&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;开销&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;316&lt;/td&gt;&lt;td&gt;overload&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;重载&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;317&lt;/td&gt;&lt;td&gt;override&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;董写&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;318&lt;/td&gt;&lt;td&gt;package&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;包&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;319&lt;/td&gt;&lt;td&gt;pair&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;一对&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;320&lt;/td&gt;&lt;td&gt;palette&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调色板&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;321&lt;/td&gt;&lt;td&gt;pane&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;窗格&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;322&lt;/td&gt;&lt;td&gt;parallel&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;平行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;323&lt;/td&gt;&lt;td&gt;parent&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;父&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;324&lt;/td&gt;&lt;td&gt;parentheses&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;括号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;325&lt;/td&gt;&lt;td&gt;parse&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;解析&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;326&lt;/td&gt;&lt;td&gt;part&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;部分&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;327&lt;/td&gt;&lt;td&gt;performance&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;性能&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;328&lt;/td&gt;&lt;td&gt;persistence&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;持久性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;329&lt;/td&gt;&lt;td&gt;pixel&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;像素&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;330&lt;/td&gt;&lt;td&gt;platform&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;平台&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;331&lt;/td&gt;&lt;td&gt;poll&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;轮询、调查&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;332&lt;/td&gt;&lt;td&gt;polyiriorphism&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;多态性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;333&lt;/td&gt;&lt;td&gt;pop&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;流行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;334&lt;/td&gt;&lt;td&gt;port&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;端口&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;335&lt;/td&gt;&lt;td&gt;postfix&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;后缀&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;336&lt;/td&gt;&lt;td&gt;precedence&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;优先级&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;337&lt;/td&gt;&lt;td&gt;prefix&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;前缀&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;338&lt;/td&gt;&lt;td&gt;preprocessor&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;预处理器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;339&lt;/td&gt;&lt;td&gt;prime&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;主要&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;340&lt;/td&gt;&lt;td&gt;primitive&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;原始的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;341&lt;/td&gt;&lt;td&gt;print&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;打印&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;342&lt;/td&gt;&lt;td&gt;printer&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;打印机&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;343&lt;/td&gt;&lt;td&gt;priority&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;优先级&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;344&lt;/td&gt;&lt;td&gt;procedural&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;程序上的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;345&lt;/td&gt;&lt;td&gt;procedure&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;过程&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;346&lt;/td&gt;&lt;td&gt;process&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;过程&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;347&lt;/td&gt;&lt;td&gt;profile&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;配取件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;348&lt;/td&gt;&lt;td&gt;profiler&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;分析器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;349&lt;/td&gt;&lt;td&gt;programmer&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;程序员&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;350&lt;/td&gt;&lt;td&gt;programming&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;编程&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;351&lt;/td&gt;&lt;td&gt;progress&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;进展&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;352&lt;/td&gt;&lt;td&gt;project&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;顶目&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;353&lt;/td&gt;&lt;td&gt;property&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;属性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;354&lt;/td&gt;&lt;td&gt;protocol&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;协议&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;355&lt;/td&gt;&lt;td&gt;pseudo&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;伪&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;356&lt;/td&gt;&lt;td&gt;qualified&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;合格的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;357&lt;/td&gt;&lt;td&gt;qualifier&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;限定符&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;358&lt;/td&gt;&lt;td&gt;quality&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;质量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;359&lt;/td&gt;&lt;td&gt;queue&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;队列&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;360&lt;/td&gt;&lt;td&gt;radian&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;弧度&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;361&lt;/td&gt;&lt;td&gt;radio&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;单选、广播&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;362&lt;/td&gt;&lt;td&gt;raise&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;提高&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;363&lt;/td&gt;&lt;td&gt;random&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;随机&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;364&lt;/td&gt;&lt;td&gt;range&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;范围&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;365&lt;/td&gt;&lt;td&gt;rank&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;排名&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;366&lt;/td&gt;&lt;td&gt;raw&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;生&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;367&lt;/td&gt;&lt;td&gt;re&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;再保险&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;368&lt;/td&gt;&lt;td&gt;record&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;记录&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;369&lt;/td&gt;&lt;td&gt;recordset&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;记录集&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;370&lt;/td&gt;&lt;td&gt;refactoring&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;重构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;371&lt;/td&gt;&lt;td&gt;reflection&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;反射&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;372&lt;/td&gt;&lt;td&gt;register&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;注册&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;373&lt;/td&gt;&lt;td&gt;relational&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;关系&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;374&lt;/td&gt;&lt;td&gt;represent&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;代表&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;375&lt;/td&gt;&lt;td&gt;resolve&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;解决&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;376&lt;/td&gt;&lt;td&gt;restriction&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;限制&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;377&lt;/td&gt;&lt;td&gt;robust&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;健壮的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;378&lt;/td&gt;&lt;td&gt;robustness&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;鲁棒性&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;379&lt;/td&gt;&lt;td&gt;routine&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;例程、常规&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;380&lt;/td&gt;&lt;td&gt;rvalue&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;右值&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;381&lt;/td&gt;&lt;td&gt;save&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;保存&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;382&lt;/td&gt;&lt;td&gt;schedule&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;时间表、日程&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;383&lt;/td&gt;&lt;td&gt;scheduler&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;调度器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;384&lt;/td&gt;&lt;td&gt;schema&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;模式、架构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;385&lt;/td&gt;&lt;td&gt;scheme&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;模式、方案&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;386&lt;/td&gt;&lt;td&gt;screen&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;屏幕&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;387&lt;/td&gt;&lt;td&gt;scroll&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;滚动&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;388&lt;/td&gt;&lt;td&gt;semantics&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;语义&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;389&lt;/td&gt;&lt;td&gt;sequential&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;顺序、相继、序列&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;390&lt;/td&gt;&lt;td&gt;serial&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;串行&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;391&lt;/td&gt;&lt;td&gt;serialization&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;序列化&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;392&lt;/td&gt;&lt;td&gt;set&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;设置、集合&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;393&lt;/td&gt;&lt;td&gt;setter&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;设置器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;394&lt;/td&gt;&lt;td&gt;signal&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;信号&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;395&lt;/td&gt;&lt;td&gt;signature&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;签名&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;396&lt;/td&gt;&lt;td&gt;slider&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;滑块&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;397&lt;/td&gt;&lt;td&gt;slot&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;槽&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;398&lt;/td&gt;&lt;td&gt;smart&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;智能的&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;399&lt;/td&gt;&lt;td&gt;snapshot&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;快照&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;400&lt;/td&gt;&lt;td&gt;software&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;软件&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;401&lt;/td&gt;&lt;td&gt;solution&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;解决方案&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;402&lt;/td&gt;&lt;td&gt;source&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;源&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;403&lt;/td&gt;&lt;td&gt;splitter&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;分离器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;404&lt;/td&gt;&lt;td&gt;statement&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;声明&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;405&lt;/td&gt;&lt;td&gt;status&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;状态&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;406&lt;/td&gt;&lt;td&gt;stream&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;流&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;407&lt;/td&gt;&lt;td&gt;string&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;字符串&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;408&lt;/td&gt;&lt;td&gt;structure&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;结构&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;409&lt;/td&gt;&lt;td&gt;subscript&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;下标&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;410&lt;/td&gt;&lt;td&gt;subtype&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;子类型&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;411&lt;/td&gt;&lt;td&gt;support&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;支持&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;412&lt;/td&gt;&lt;td&gt;suspend&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;暂停&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;413&lt;/td&gt;&lt;td&gt;switch&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;开关、切换&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;414&lt;/td&gt;&lt;td&gt;symbol&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;符号、象征、标志&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;415&lt;/td&gt;&lt;td&gt;syntax&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;语袪&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;416&lt;/td&gt;&lt;td&gt;system&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;系统&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;417&lt;/td&gt;&lt;td&gt;tag&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;标签&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;418&lt;/td&gt;&lt;td&gt;target&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;目标&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;419&lt;/td&gt;&lt;td&gt;task&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;任务&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;420&lt;/td&gt;&lt;td&gt;tasking&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;任务&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;421&lt;/td&gt;&lt;td&gt;teirporary&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;临时&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;422&lt;/td&gt;&lt;td&gt;text&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;文本&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;423&lt;/td&gt;&lt;td&gt;time&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;时间&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;424&lt;/td&gt;&lt;td&gt;to&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;到、为&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;425&lt;/td&gt;&lt;td&gt;tree&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;树&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;426&lt;/td&gt;&lt;td&gt;unwinding&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;解除&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;427&lt;/td&gt;&lt;td&gt;up&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;向上&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;428&lt;/td&gt;&lt;td&gt;user&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;用户&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2&gt;工具介绍&lt;/h2&gt; &lt;p&gt;AntConc 使用工具软件可以把一本电子档中的单词全部提取出来，并分析其出现频率&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2019/04/17cagstsoqhfdos7lvakigb145.jpg" alt="" title="" /&gt;&lt;/p&gt; &lt;h2&gt;Java异常&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; //算术异常    ArithmeticExecption     //空指针异常类    NullPointerException     //类型强制转换异常    ClassCastException     //数组负下标异常    NegativeArrayException     //数组下标越界异常    ArrayIndexOutOfBoundsException     //违背安全原则异常    SecturityException     //文件已结束异常    EOFException     //文件未找到异常    FileNotFoundException     //字符串转换为数字异常    NumberFormatException     //操作数据库异常    SQLException     //输入输出异常    IOException     //方法未找到异常    NoSuchMethodException     //抽象方法错误，当应用试图调用抽象方法时抛出     java.lang.AbstractMethodError     //断言错误，用来指示一个断言失败的情况    java.lang.AssertionError     //类循环依赖错误，若检测到类之间循环依赖则抛出该异常    java.lang.ClassCircularityError     //类格式错误    java.lang.ClassFormatError     //错误基类，标识严重程序运行问题，不应被应用程序捕获的反常情况    java.lang.Error     //非法访问错误，违反域或方法的可见性声明    java.lang.IllegalAccessError     //不兼容的类变化错误，当正在执行的方法所依赖的类定义发生了不兼容的改变时，抛出该异常    java.lang.IncompatibleClassChangeError     //实例化错误，构造一个抽象类或者接口时抛出该异常    java.lang.InstantiationError     //内部错误    java.lang.InternalError     //链接错误    java.lang.LinkageError     //未找到类定义错误，找不到该类的定义时抛出该错误    java.lang.NoClassDefFoundError     //域（成员变量，字段）不存在错误    java.lang.NoSuchFieldError     //方法不存在错误    java.lang.NoSuchMethodError     //内存不足错误    java.lang.OutOfMemoryError     //堆栈溢出错误，如递归调用的层次太深    java.lang.StackOverflowError     //线程已结束    java.lang.ThreadDeath     //未知错误    java.lang.UnknownError     //未满足的链接错误    java.lang.UnsatisfiedLinkError     //不支持的类版本错误    java.lang.UnsupportedClassVersionError     //验证错误    java.lang.VerifyError     //虚拟机错误    java.lang.VirtualMachineError     //算术条件异常，如整数除零    java.lang.ArithmeticException     //数组索引越界异常    java.lang.ArrayIndexOutOfBoundsException     //数组存储异常，存放非数组声明类型    java.lang.ArrayStoreException     //类型转换异常    java.lang.ClassCastException     //找不到类异常    java.lang.ClassNotFoundException     //克隆不支持异常，如没有实现Cloneable接口    java.lang.CloneNotSupportedException     //枚举常量不存在异常    java.lang.EnumConstantNotPresentException     //根异常    java.lang.Exception     //非法访问异常    java.lang.IllegalAccessException     //非法的监控状态异常    java.lang.IllegalMonitorStateException     //非法的状态异常    java.lang.IllegalStateException     //非法的线程状态异常    java.lang.IllegalThreadStateException     //索引越界异常    java.lang.IndexOutOfBoundsException     //实例化异常    java.lang.InstantiationException     //被中断异常    java.lang.InterruptedException     //数组大小为负值异常    java.lang.NegativeArraySizeException     //属性不存在异常，当访问某个类的不存在的属性时抛出该异常    java.lang.NoSuchFieldException     //方法不存在异常    java.lang.NoSuchMethodException     //空指针异常。当应用试图在要求使用对象的地方使用了null时，抛出该异常    java.lang.NullPointerException     //数字格式异常    java.lang.NumberFormatException     //运行时异常    java.lang.RuntimeException     //安全异常    java.lang.SecurityException     //字符串索引越界异常    java.lang.StringIndexOutOfBoundsException     //类型不存在异常    java.lang.TypeNotPresentException     //不支持的方法异常    java.lang.UnsupportedOperationException     //Jsp页面异常    javax.servlet.jsp.JspException     //JSP引擎解析异常    org.apache.jasper.JasperException     //Servlet异常    javax.servlet.ServletException     //对象已被删除异常    org.hibernate.ObjectDeletedException  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;提高英文水平的建议与资源&lt;/h2&gt; &lt;h3&gt;提高英文水平的个人建议&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;(1)、自己起一个英文名称，作为网名和登录名。 (2)、将自己电脑中的中文版或汉化软件换成英文版，可以挑战一下英文操作系统，建议从英文手机系统开始。 (3)、选择一本适合自己的计算机英文教材。 (4)、保证一年至少读1-2本原版影印的计算机书，并坚持读完。 (5)、上网尽量多访问英文技术论坛和网站。不使用汉化帮助。 (6)、在程序中使用英文名称，坚持不用中文或汉语拼音作为变量名、字段名、文件和文件夹名（上过中文路径当的朋友是不是深有同感） (7)、每月至少看1-2部原版电影，双语初学，去字幕练习听力，反复。 (8)、每天坚持记录和复习遇到的生词，对于缩略语，一定要搞清楚每个英文字母的含义。 (9)、最重要的一条：相信自己能学会（世界上最难的中文你都可以学会英文肯定没问题）,遇到英文不逃避、坚持、重复、再坚持（制定计划）。 (10)、以前一段时间我很喜欢去Youtube看新闻与自己喜欢的节目，主要是看评论，原汁原味，还很有趣；后来就被墙了... 欢迎更多建议与方法，谢谢了！ &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;翻译与双语视频字幕网站&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;http://fanyi.baidu.com/ 百度翻译，比以前要进步很多了，可以选择意思 http://fanyi.youdao.com/ 有道翻译，计算翻译比较准确 http://translate.google.cn/ google翻译，老牌翻译，大，强，但这几年变化不大 http://www.yinbiao5.com 音标网，支持批量单词音标 http://dict.cn/ 海词，例句多 http://assrt.net/ 射手网 双语字幕 http://www.zimuku.net/ 字幕库 http://subhd.com/main Sub HD 字幕站 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 20 Apr 2019 18:42:00 GMT</pubDate>
    </item>
    <item>
      <title>Mybatis 执行后 参数 未拼接到 SQL语句中</title>
      <link>https://maruifu.cn/article/94</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;每次生产有问题,看日志拿到sql,如果参数太多,把参数拼接到sql中费很长时间,于是写了一个小工具！&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;直接上代码&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; package com.mantis.hc.sale.service.pay.util;   import java.awt.event.ActionEvent;  import java.awt.event.ActionListener; import java.util.regex.Matcher; import java.util.regex.Pattern;  import javax.swing.*;  public class sqlTools extends JFrame implements ActionListener {         private JPanel jp=new JPanel();      private JLabel[] jlArray={new JLabel(&amp;quot;SQL&amp;quot;),              new JLabel(&amp;quot;字段值&amp;quot;),new JLabel(&amp;quot;结果&amp;quot;),new JLabel(&amp;quot;&amp;quot;) };      private JButton[] jbArray={new JButton(&amp;quot;执行&amp;quot;),              new JButton(&amp;quot;清空&amp;quot;)};      private JTextField jtxtSql =new JTextField();      private JTextField JTextValue= new JTextField();       private JTextArea resultSQL= new JTextArea();      public sqlTools(){         jp.setLayout(null);          for(int i=0;i&amp;lt;2;i++){              jlArray[i].setBounds(30, 20+i*50, 80, 26);              jbArray[i].setBounds(120+i*110, 660, 80,26);              jp.add(jlArray[i]);              jp.add(jbArray[i]);              jbArray[i].addActionListener(this);          }           for(int i=2;i&amp;lt;3;i++){              jlArray[i].setBounds(30, 20+i*50, 80, 26);              jp.add(jlArray[i]);         }         jtxtSql.setBounds(80,20,500,30);         jp.add(jtxtSql);         jtxtSql.addActionListener(this);         JTextValue.setBounds(80,70,500,30);         jp.add(JTextValue);         JTextValue.addActionListener(this);         resultSQL.setBounds(80,120,500,500);         resultSQL.setLineWrap(true);         jp.add(resultSQL);         jlArray[3].setBounds(10, 250, 700, 30);         jp.add(jlArray[3]);         this.add(jp);         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         this.setTitle(&amp;quot;小马哥工具&amp;quot;);         this.setResizable(false);         this.setBounds(100, 230, 700, 800);         this.setVisible(true);      }       public void actionPerformed(ActionEvent e){            if(jtxtSql.getText().equals(&amp;quot;&amp;quot;)&amp;amp;&amp;amp;String.valueOf(JTextValue.getText()).equals(&amp;quot;&amp;quot;)){              resultSQL.setText(&amp;quot;参数不能为空!,请重新参数!&amp;quot;);          }else{              try{                 String resultStr =  getResultSQL(jtxtSql.getText(),JTextValue.getText());                 resultSQL.setText(resultStr);             }catch (Exception a){                 System.out.println(a.getMessage());                 resultSQL.setText(&amp;quot;参数错误,请检查参数!&amp;quot;);             }          }      }      //主函数入口     public static void main(String[] args) {          new sqlTools();      }             public static  String getResultSQL(String  text,String  str) {          Pattern pattern = Pattern.compile(&amp;quot;\\?&amp;quot;);         StringBuffer buffer = new StringBuffer();          //参数转换成数组         String[] words = ClearBracket(str).split(&amp;quot;,&amp;quot;);         for (int i = 0; i &amp;lt; words.length; i++) {              Matcher matcher = pattern.matcher(text);             while (matcher.find()) {                 //?号 替换成数据中对应的数据                 matcher.appendReplacement(buffer, &amp;quot;'&amp;quot;+words[i++].trim()+&amp;quot;'&amp;quot;);             }             matcher.appendTail(buffer);         }          return buffer.toString();       }         //删除括号及括号中的数据     private static String ClearBracket(String context) {         String pattern = &amp;quot;\\([^)]*\\)&amp;quot;;         context = context.replaceAll(pattern, &amp;quot;&amp;quot;);         return context;     }          }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;生成class 文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;javac ResSql.java  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;打包成jar&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;jar cvf sqlTools.jar ResSql.class  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;修改 MANIFEST.MF 文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; Manifest-Version: 1.0 Created-By: 1.8.0_131 (Oracle Corporation) Main-Class: ResSql &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;打包压缩 ZIP 修改后缀 JAR&lt;/h2&gt; &lt;h2&gt;编写Bat文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt; @echo off  SET javaw=&amp;quot;%JAVA_HOME%\bin\javaw&amp;quot;  start javaw -jar &amp;quot;sqlTools.jar&amp;quot;   exit  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 18 Apr 2019 15:02:22 GMT</pubDate>
    </item>
    <item>
      <title>sql语句的执行顺序以及流程（详细掌握）</title>
      <link>https://maruifu.cn/article/84</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;程序员对sql语句的执行顺序的透彻掌握，是避免编程中各种bug和错误，歧义语句的不二法则。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h3&gt;SQL Select 语句完整的执行顺序：&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;1、from 子句组装来自不同数据源的数据； 2、where 子句基于指定的条件对记录行进行筛选；  3、group by 子句将数据划分为多个分组；  4、使用聚集函数进行计算；  5、使用 having 子句筛选分组；  6、计算所有的表达式；  7、select 的字段；  8、使用 order by 对结果集进行排序。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;SQL 语言不同于其他编程语言的最明显特征是处理代码的顺序。 在大多数据库语言中，代码按编码顺序被处理。但在 SQL 语句中，第一个被处理的子句式 FROM，而不是第一出现的 SELECT。&lt;/p&gt; &lt;h3&gt;SQL 查询处理的步骤序号：&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;(1) FROM &amp;lt;left_table&amp;gt;  (2) &amp;lt;join_type&amp;gt; JOIN &amp;lt;right_table&amp;gt;  (3) ON &amp;lt;join_condition&amp;gt;  (4) WHERE &amp;lt;where_condition&amp;gt;  (5) GROUP BY &amp;lt;group_by_list&amp;gt; (6) WITH {CUBE | ROLLUP}  (7) HAVING &amp;lt;having_condition&amp;gt;  (8) SELECT (9) DISTINCT  (9) ORDER BY &amp;lt;order_by_list&amp;gt;  (10) &amp;lt;TOP_specification&amp;gt; &amp;lt;select_list&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上每个步骤都会产生一个虚拟表，该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应 用程序或者外部查询)不可用。只有最后一步生成的表才会会给调用者。如果没有在查询中指定某一个子句， 将跳过相应的步骤。&lt;/p&gt; &lt;h3&gt;逻辑查询处理阶段简介：&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;1、 FROM：对 FROM 子句中的前两个表执行笛卡尔积(交叉联接)，生成虚拟表 VT1。  2、 ON：对 VT1 应用 ON 筛选器，只有那些使为真才被插入到 TV2。   3、 OUTER (JOIN):如果指定了 OUTER JOIN(相对于 CROSS JOIN 或 INNER JOIN)，保留表中未找到 匹配的行将作为外部行添加到 VT2，生成 TV3。如果 FROM 子句包含两个以上的表，则对上一个联接生成的 结果表和下一个表重复执行步骤 1 到步骤 3，直到处理完所有的表位置。  4、 WHERE：对 TV3 应用 WHERE 筛选器，只有使为 true 的行才插入 TV4。   5、 GROUP BY：按 GROUP BY 子句中的列列表对 TV4 中的行进行分组，生成 TV5。   6、 CUTE|ROLLUP：把超组插入 VT5，生成 VT6。   7、 HAVING：对 VT6 应用 HAVING 筛选器，只有使为 true 的组插入到 VT7。   8、 SELECT：处理 SELECT 列表，产生 VT8。   9、 DISTINCT：将重复的行从 VT8 中删除，产品 VT9。  10、 ORDER BY：将 VT9 中的行按 ORDER BY 子句中的列列表顺序，生成一个游标(VC10)。   11、 TOP：从 VC10 的开始处选择指定数量或比例的行，生成表 TV11，并返回给调用者。 where 子句中的条件书写顺序 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sun, 24 Feb 2019 14:50:00 GMT</pubDate>
    </item>
    <item>
      <title>Linux中执行脚本No such file or directory。</title>
      <link>https://maruifu.cn/article/82</link>
      <content:encoded>&lt;p&gt;一、分析&lt;/p&gt; &lt;p&gt;这是不同系统编码格式引起的：在windows系统中编辑的.sh文件可能有不可见字符，所以在Linux系统下执行会报以上异常信息。&lt;/p&gt; &lt;p&gt;二、解决&lt;/p&gt; &lt;p&gt;1）在windows下转换： 利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换，再放到Linux中执行。转换方式如下（UltraEdit）：File–&amp;gt;Conversions–&amp;gt;DOS-&amp;gt;UNIX即可。&lt;/p&gt; &lt;p&gt;2）直接在Linux中转换（推荐做法）：&lt;/p&gt; &lt;p&gt;首先要确保文件有可执行权限&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#sh&amp;gt; chmod a+x filename 1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后修改文件格式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#sh&amp;gt; vi filename 1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;利用如下命令查看文件格式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;:set ff 或 :set fileformat 1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看到如下信息&lt;/p&gt; &lt;pre&gt;&lt;code&gt;fileformat=dos 或 fileformat=unix &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;利用如下命令修改文件格式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;:set ff=unix 或 :set fileformat=unix  :wq (存盘退出)  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最后再执行文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;#sh&amp;gt;./filename &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 18 Feb 2019 14:49:00 GMT</pubDate>
    </item>
    <item>
      <title>今天遇到的一个奇葩的NoClassFound的问题</title>
      <link>https://maruifu.cn/article/81</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;with root cause  java.lang.NoClassDefFoundError&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;p&gt;nohup的日志中报错&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java.lang.NoClassDefFoundError: org/apache/catalina/core/ApplicationContext$DispatchData  2018-09-01 20:21:55.017 |-ERROR [http-nio-28082-exec-9] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] [181] -| Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause java.lang.NoClassDefFoundError: org/springframework/boot/actuate/trace/WebRequestTraceFilter$CustomStatusResponseWrapper &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最后居然是因为该jar是root用户启动的, 而Jenkins上使用的是rc用户,Jenkins部署的时候无法通过rc将该进程杀死.但是有的接口是OK的,有的接口就会报上面的问题&lt;/p&gt; &lt;p&gt;将该进程杀死,用Jenkins启动后,一切正常.&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 03 Sep 2018 02:34:44 GMT</pubDate>
    </item>
    <item>
      <title>Mysql 根据时间戳按年月日分组统计(做个收藏)</title>
      <link>https://maruifu.cn/article/80</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Mysql 根据时间戳按年月日分组统计&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;pre&gt;&lt;code&gt;        create_time时间格式          SELECT DATE_FORMAT(create_time,'%Y%u') weeks,COUNT(id) COUNT FROM role GROUP BY weeks;          SELECT DATE_FORMAT(create_time,'%Y%m%d') days,COUNT(id) COUNT FROM role GROUP BY days;          SELECT DATE_FORMAT(create_time,'%Y%m') months,COUNT(id) COUNT FROM role GROUP BY months                    create_time时间戳格式          SELECT FROM_UNIXTIME(create_time,'%Y%u') weeks,COUNT(id) COUNT FROM role GROUP BY weeks;          SELECT FROM_UNIXTIME(create_time,'%Y%m%d') days,COUNT(id) COUNT FROM role GROUP BY days;          SELECT FROM_UNIXTIME(create_time,'%Y%m') months,COUNT(id) COUNT FROM role GROUP BY months  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Sat, 18 Aug 2018 07:14:56 GMT</pubDate>
    </item>
    <item>
      <title>对list中的对象属性排序</title>
      <link>https://maruifu.cn/article/79</link>
      <content:encoded>&lt;h1&gt;对list中的对象属性排序&lt;/h1&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;今天遇到一个排序问题觉得挺值得分享的，一个集合，集合存储着若干对象，对象有若干属性，希望按照对象的某个属性排序，排序完成，list的存储顺序也是按照这个属性排完以后的顺序。&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;p&gt;代码如下：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;     public class User {  private String username;  private int age;    public String getUsername() {      return username;  }    public void setUsername(String username) {      this.username = username;  }    public int getAge() {      return age;  }    public void setAge(int age) {      this.age = age;  }    public User() {  }  }    public class ListSortTest {  public static void main(String args[]){      List&amp;lt;User&amp;gt; userlist=new ArrayList&amp;lt;User&amp;gt;();      User user1=new User();      User user2=new User();      User user3=new User();      User user4=new User();      user1.setAge(10);      userlist.add(user1);      user2.setAge(34);      userlist.add(user2);      user3.setAge(19);      userlist.add(user3);      user4.setAge(6);      userlist.add(user4);      System.out.println(&amp;quot;排序前&amp;quot;);      for(User user:userlist){            System.out.print(user.getAge() + &amp;quot;-&amp;quot;);      }      System.out.println(&amp;quot;\n&amp;quot;);      System.out.println(&amp;quot;排序后&amp;quot;);      for(User user:getSortList(userlist)){          System.out.print(user.getAge() + &amp;quot;-&amp;quot;);      }    }    public static List&amp;lt;User&amp;gt; getSortList(List&amp;lt;User&amp;gt; list){      Collections.sort(list, new Comparator&amp;lt;User&amp;gt;() {          @Override          public int compare(User o1, User o2) {              if(o1.getAge()&amp;gt;o2.getAge()){                  return 1;              }              if(o1.getAge()==o2.getAge()){                  return 0;              }              return -1;          }      });      return list;  }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出结果：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;排序前 10-34-19-6-   排序后 6-10-19-34- &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们再来看一下它实现的代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  public static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c) {      Object[] a = list.toArray();      Arrays.sort(a, (Comparator)c);      ListIterator i = list.listIterator();      for (int j=0; j&amp;lt;a.length; j++) {          i.next();          i.set(a[j]);      }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而Arrays.sort使用的是冒泡和归并排序，默认是归并排序，所以排序速度还是很快的.&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 18 Jul 2018 11:53:00 GMT</pubDate>
    </item>
    <item>
      <title>Java并发问题总结</title>
      <link>https://maruifu.cn/article/78</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Java并发问题总结!&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;Java内存模型&lt;/h2&gt; &lt;p&gt;所有变量都存储在主内存中。这里的主内存只是虚拟机内存的一部分，可以和物理主内存类比。每条线程都有自己的工作内存。工作内存可以和处理器高速缓存类比。工作内存中保存了主内存中变量的拷贝，线程所有的操作只能在工作内存中进行，不同线程不能访问对方的工作内存，只能通过更新到主内存中的方式来传递线程间的变量值。&lt;/p&gt; &lt;p&gt;主内存与工作内存间的交互操作都具有原子性，包括&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/bingfayuanxixingcaozuo.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;其中read和load之间，store和write之间必须按顺序执行，但是不要求连续执行，即中间可以插入其他指令。&lt;/p&gt; &lt;h2&gt;并发的三个问题&lt;/h2&gt; &lt;h3&gt;原子性&lt;/h3&gt; &lt;p&gt;指的是不能被线程调度机制中断的操作，它会在上下文切换之前执行完毕。由于read，load，store，write，use，assign都能够保证原子性，故对一个基本类型变量的访问和赋值可以看作原子操作。对于synchronized块之间的操作也具有原子性。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;x = 1; // 具有原子性 y = x; // 2个指令，use了x的值，再assign到y x++; // 4个指令，use了x的值，生成常数1，x加1，再assign到x &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;可见性&lt;/h3&gt; &lt;p&gt;指的是当一个线程修改了共享变量值，其他线程能够立即得知这个修改。&lt;/p&gt; &lt;p&gt;普通变量的修改首先发生在本线程的工作内存中，这会导致各个工作内存的不一致性。当一个线程结束后会将各自的工作内存同步回主内存，另一个线程读取这个变量时会从主内存中读取它的新值。&lt;/p&gt; &lt;p&gt;volatile变量也是同样的过程，只是它修改后立即同步回主内存，并通知其他工作内存中的此变量失效。如果其他线程需要使用此变量时，只能从主内存中重新读取它的新值。这就保证了多线程下的变量可见性。&lt;/p&gt; &lt;p&gt;synchronized同步块也具有可见性。这是由于对一个变量unlock之前，必须先将它同步回主内存中。&lt;/p&gt; &lt;p&gt;final修饰的变量也具有可见性。当一个final变量被初始化后(构造器完成之前没有将this引用传递出去)，此变量在其他线程中可见。&lt;/p&gt; &lt;h3&gt;有序性&lt;/h3&gt; &lt;p&gt;指的是机器会对指令进行重排序来达到运行时的优化。这就导致了代码书写上的先后顺序不能在执行时得到保证，但是在单线程内看，程序执行的结果和按照串行执行的结果保持一致。而使用多线程时执行结果就不能保证了。Java可以有两种方法保证线程间的有序性&lt;/p&gt; &lt;p&gt;volatile可以防止指令随意的重排序，它的作用相当于一个内存屏障，也就是重排序时不能将内存屏障之后的指令排在它之前。&lt;/p&gt; &lt;p&gt;synchronized同步块可以保证有序，是因为同一时刻只允许一个线程对某个变量进行lock操作。因此多个线程只能有序的进入同一个同步块。&lt;/p&gt; &lt;h2&gt;volatile关键字&lt;/h2&gt; &lt;p&gt;volatile是最轻量级的同步机制，但是它只保证了被修饰变量的可见性和有序性，而不能保证原子性，从而不能解决很多并发同步问题。&lt;/p&gt; &lt;h3&gt;具有两个特性&lt;/h3&gt; &lt;p&gt;可见性，一旦某个线程中的volatile变量被修改，即store和write 指令执行后，所有线程都可以得到最新的变量值。注意这里是指令执行，而不是Java的语句执行，一条非原子性的Java语句总是对应多条指令，所以这条语句所带来的改变不具有可见性。 有序性，防止指令随意的重排序优化。通过在汇编代码中加入lock操作，使变量的修改写回主内存，即执行了store和write指令。这意味着写之前的所有操作都必须执行完成，从而达到了内存屏障的作用。同时它还会使其他工作内存中的该变量无效，其他线程需要重新从主内存中读取此变量。&lt;/p&gt; &lt;h3&gt;应用场合&lt;/h3&gt; &lt;p&gt;由于volatile能够保证可见性和有序性，唯一不能保证原子性，因此如果一个操作本身具有原子性，那么使用volatile修饰后就可以保证并发的同步性。应用场合有两个&lt;/p&gt; &lt;p&gt;变量的赋值不依赖于它的当前值或别的变量的当前值，即直接使用assign指令而没有使用use指令，具有原子性 保证只有一个线程对变量进行修改，而别的线程只进行读取，读取值不一定是最新的，但修改不会出错&lt;/p&gt; &lt;h2&gt;synchronized关键字&lt;/h2&gt; &lt;p&gt;可以解决所有并发问题，但是容易造成滥用而导致并发性能不高。可以作为方法的修饰符，表示要进入方法时需要获取本对象的锁，也可以使用synchronized(object){...}对代码块加锁，表示要进入代码块时需要获取object的锁。 一旦有一个线程获取了锁而进入了同步块中，所有的其他线程都会进入等锁池而阻塞。这有时会导致不必要的阻塞时间。同时，Java线程是映射到操作系统原生线程上的，阻塞和唤醒都需要从用户态转换到内核态，要花费较多处理器时间。而volatile变量的读性能消耗与普通变量几乎相同，但是写操作稍慢，因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。&lt;/p&gt; &lt;h2&gt;重入锁ReentrantLock&lt;/h2&gt; &lt;p&gt;ReentrantLock可以显式的创建，锁定和释放，与synchronized的内建锁复杂但是更灵活，尤其是进入同步块后如果抛出异常，可以进行清理工作。另外还可以实现一些高级功能，包括等待可中断（线程可以放弃等待而做其他事情），公平锁（按照申请锁的时间获取锁），多条件绑定（通过调用newCondition()方法添加多个Condition）。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; Lock lock = new ReentrantLock(); lock.lock(); try {     ... } finally {     lock.unlock(); }  ReentrantLock lock = new ReentrantLock(); boolean captured = lock.tryLock(); try {     ... } finally {     if (captured) lock.unlock(); }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Atomic类&lt;/h2&gt; &lt;p&gt;加锁属于阻塞同步，即无论共享数据是否真的出现竞争都会加锁，这是悲观的并发策略。而利用硬件指令还可以实现非阻塞同步，这是一种基于冲突检测的乐观并发策略。它可以先操作，如果没有其他线程争用共享数据则操作成功，而如果产生了冲突，则不断重试直到操作成功。这涉及一条处理器指令compare-and-swap（CAS），它具有原子性，表示变量符合旧值时才会用新值更新变量，否则不更新，最后都返回旧值。 例如Java8中AtomicInteger类的incrementAndGet()方法，会用到sun/misc/Unsafe类中的getAndAddInt()方法，其中的compareAndSwapInt()就是CAS操作。下述代码的意义是对于实例var1在偏移var2处的旧值为var5，如果即将要赋值的时候发现获取的值不符合var5（CAS指令操作），说明此时有其他线程已经修改了这个变量，于是继续获取新的var5，直到赋值前获取的值符合var5，则用var5+var4更新var5。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; public final int getAndAddInt(Object var1, long var2, int var4) {         int var5;         do {             var5 = this.getIntVolatile(var1, var2);         } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));          return var5;     }  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;不使用同步的情况&lt;/h2&gt; &lt;p&gt;可重入代码不需要同步，因为它不使用共享变量，且所有的状态量都由参数传入，所以在任何时刻中断它再返回后不会出现错误，这就保证了线程安全。&lt;/p&gt; &lt;p&gt;如果能保证共享变量只在一个线程中可见，同样也不需要同步，但是这样的应用比较少见。&lt;/p&gt; &lt;p&gt;利用ThreadLocal可以根除了对变量的共享，它可以为使用相同变量的每个线程创建不同的存储。每个线程使用的都是独立的变量，当然不会有同步的问题。但是一个线程是不能访问到另一个线程的ThreadLocal变量，尽管只创建过一个ThreadLocal变量的实例。一个线程只能使用get和set改变本线程内该变量的值，不同线程中该变量值互不影响。具体实现中，每个Thread线程对象都由一个ThreadLocal.ThreadLocalMap，其键为ThreadLocal.ThreadLocalHashCode，值就是本地线程变量，各个线程的Map是独立的。&lt;/p&gt; &lt;h2&gt;锁优化&lt;/h2&gt; &lt;h3&gt;自旋锁&lt;/h3&gt; &lt;p&gt;阻塞线程比较耗时，是因为挂起和恢复线程都需要转换内核态，而锁定共享数据往往只持续很短的时间。因此有时只需要让线程执行一个忙循环（自旋）等待，但是不放弃处理器执行，就可以获取锁。前提是等待时间不能太长，自适应自旋锁可以调整自旋的时间。&lt;/p&gt; &lt;h3&gt;锁消除&lt;/h3&gt; &lt;p&gt;如果代码上要求同步，但是经过逃逸分析发现不可能存在共享数据竞争，因而可以将锁进行消除。有些代码的同步可能不是人为加入的，而是源码自带的。&lt;/p&gt; &lt;h3&gt;锁粗化&lt;/h3&gt; &lt;p&gt;如果一系列加锁和解锁是对同一个对象连续进行的，就可以将同步范围扩大到整个序列的外部，这样就可以进行一次加锁和解锁了。&lt;/p&gt; &lt;h3&gt;轻量级锁&lt;/h3&gt; &lt;p&gt;认为大部分锁在同步时间内是不存在竞争的。通过利用对象头信息Mark Word和CAS操作判断对象是否加锁，如果没有，则直接进入同步块执行，这个判断过程就是轻量级锁；否则说明的这个加锁的对象有竞争，轻量级锁就要膨胀为重量级锁，也就是使用互斥量的一般锁。 解锁也要使用CAS操作，将原来的Mark Word的记录替换回来，如果替换成功说明在此期间没有线程尝试过获取该锁，从而解锁完成；如果失败，则一定有线程尝试过获取该锁，所以在解锁完成后还要唤醒等锁的线程。&lt;/p&gt; &lt;h3&gt;偏向锁&lt;/h3&gt; &lt;p&gt;在轻量级锁的基础上，如果没有竞争，线程将CAS操作也取消，且这个偏向锁会偏向第一个获得它的线程。如果执行过程中该锁没有被其他线程获取，则持有偏向锁的线程永远不用同步。但是一旦有线程尝试获取该锁时，偏向模式被撤销，将锁对象恢复为未锁定或者轻量级锁。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 17 Apr 2018 07:14:00 GMT</pubDate>
    </item>
    <item>
      <title>HashMap底层实现详解</title>
      <link>https://maruifu.cn/article/77</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;前段时间面试 无论是58,还是京东 还是阿里 都问了Map的底层实现,小马哥又仔细看了看源码!&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;HashMap概述：&lt;/h2&gt; &lt;p&gt;　　HashMap是基于哈希表的Map接口的非同步实现（Hashtable跟HashMap很像，唯一的区别是Hashtalbe中的方法是线程安全的，也就是同步的）。此实现提供所有可选的映射操作，并允许使用null值和null键。此类不保证映射的顺序，特别是它不保证该顺序恒久不变。&lt;/p&gt; &lt;h2&gt;HashMap的数据结构：&lt;/h2&gt; &lt;p&gt;　　在java编程语言中，最基本的结构就是两种，一个是数组，另外一个是模拟指针（引用），所有的数据结构都可以用这两个基本结构来构造的，HashMap也不例外。HashMap实际上是一个“链表的数组”的数据结构，每个元素存放链表头结点的数组，即数组和链表的结合体。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/hashmapdicengshgixin.jpg" alt="map底层数据结构" title="map底层数据结构" /&gt;&lt;/p&gt; &lt;p&gt;从上图中可以看出，HashMap底层就是一个数组，数组中的每一项又是一个链表。当新建一个HashMap的时候，就会初始化一个数组。源码如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; /**   * The table, resized as necessary. Length MUST Always be a power of two.   */   transient Entry[] table;      static class Entry&amp;lt;K,V&amp;gt; implements Map.Entry&amp;lt;K,V&amp;gt; {       final K key;       V value;       Entry&amp;lt;K,V&amp;gt; next;       final int hash;       ……   }    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看出，Entry就是数组中的元素，每个Map.Entry就是一个key-value对，它持有一个指向下一个元素的引用，这就构成了链表。&lt;/p&gt; &lt;h2&gt;HashMap的存取实现：&lt;/h2&gt; &lt;h3&gt;存储&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public V put(K key, V value) {       // HashMap允许存放null键和null值。       // 当key为null时，调用putForNullKey方法，将value放置在数组第一个位置。       if (key == null)           return putForNullKey(value);       // 根据key的hashCode重新计算hash值。       int hash = hash(key.hashCode());       // 搜索指定hash值所对应table中的索引。       int i = indexFor(hash, table.length);       // 如果 i 索引处的 Entry 不为 null，通过循环不断遍历 e 元素的下一个元素。       for (Entry&amp;lt;K,V&amp;gt; e = table[i]; e != null; e = e.next) {           Object k;           if (e.hash == hash &amp;amp;&amp;amp; ((k = e.key) == key || key.equals(k))) {               V oldValue = e.value;               e.value = value;               e.recordAccess(this);               return oldValue;           }       }       // 如果i索引处的Entry为null，表明此处还没有Entry。       // modCount记录HashMap中修改结构的次数       modCount++;       // 将key、value添加到i索引处。       addEntry(hash, key, value, i);       return null;   }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从上面的源代码中可以看出：当我们往HashMap中put元素的时候，先根据key的hashCode重新计算hash值，根据hash值得到这个元素在数组中的位置（即下标），如果数组该位置上已经存放有其他元素了，那么在这个位置上的元素将以链表的形式存放，新加入的放在链头，最先加入的放在链尾。如果数组该位置上没有元素，就直接将该元素放到此数组中的该位置上。&lt;/p&gt; &lt;p&gt;　　addEntry(hash, key, value, i)方法根据计算出的hash值，将key-value对放在数组table的 i 索引处。addEntry 是HashMap 提供的一个包访问权限的方法（就是没有public，protected，private这三个访问权限修饰词修饰，为默认的访问权限，用default表示，但在代码中没有这个default），代码如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; void addEntry(int hash, K key, V value, int bucketIndex) {       // 获取指定 bucketIndex 索引处的 Entry        Entry&amp;lt;K,V&amp;gt; e = table[bucketIndex];       // 将新创建的 Entry 放入 bucketIndex 索引处，并让新的 Entry 指向原来的 Entry       table[bucketIndex] = new Entry&amp;lt;K,V&amp;gt;(hash, key, value, e);       // 如果 Map 中的 key-value 对的数量超过了极限       if (size++ &amp;gt;= threshold)       // 把 table 对象的长度扩充到原来的2倍。           resize(2 * table.length);   }    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当系统决定存储HashMap中的key-value对时，完全没有考虑Entry中的value，仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属，当系统决定了 key 的存储位置之后，value 随之保存在那里即可。&lt;/p&gt; &lt;p&gt;　　hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算，防止低位不变，高位变化时，造成的hash冲突。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; static int hash(int h) {       h ^= (h &amp;gt;&amp;gt;&amp;gt; 20) ^ (h &amp;gt;&amp;gt;&amp;gt; 12);       return h ^ (h &amp;gt;&amp;gt;&amp;gt; 7) ^ (h &amp;gt;&amp;gt;&amp;gt; 4);   }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们可以看到在HashMap中要找到某个元素，需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合，所以我们当然希望这个HashMap里面的 元素位置尽量的分布均匀些，尽量使得每个位置上的元素数量只有一个，那么当我们用hash算法求得这个位置的时候，马上就可以知道对应位置的元素就是我们要的，而不用再去遍历链表，这样就大大优化了查询的效率。&lt;/p&gt; &lt;p&gt;　　对于任意给定的对象，只要它的 hashCode() 返回值相同，那么程序调用 hash(int h) 方法所计算得到的 hash 码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算，这样一来，元素的分布相对来说是比较均匀的。但是，“模”运算的消耗还是比较大的，在HashMap中是这样做的：调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; static int indexFor(int h, int length) {       return h &amp;amp; (length-1);   }    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个方法非常巧妙，它通过 h &amp;amp; (table.length -1) 来得到该对象的保存位，而HashMap底层数组的长度总是 2 的n 次方，这是HashMap在速度上的优化。在 HashMap 构造器中有如下代码：&lt;/p&gt; &lt;p&gt;这段代码保证初始化时HashMap的容量总是2的n次方，即底层数组的长度总是为2的n次方。&lt;/p&gt; &lt;p&gt;　　当length总是 2 的n次方时，h&amp;amp; (length-1)运算等价于对length取模，也就是h%length，但是&amp;amp;比%具有更高的效率。&lt;/p&gt; &lt;p&gt;　　这看上去很简单，其实比较有玄机的，我们举个例子来说明：&lt;/p&gt; &lt;p&gt;　　假设数组长度分别为15和16，优化后的hash码分别为8和9，那么&amp;amp;运算后的结果如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   h &amp;amp; (table.length-1)                     hash                             table.length-1     8 &amp;amp; (15-1)：                                 0100                   &amp;amp;              1110                   =                0100     9 &amp;amp; (15-1)：                                 0101                   &amp;amp;              1110                   =                0100    -----------------------------------------------------------------------------------------------------------------------     8 &amp;amp; (16-1)：                                 0100                   &amp;amp;              1111                   =                0100     9 &amp;amp; (16-1)：                                 0101                   &amp;amp;              1111                   =                0101    ----------------------------------------------------------------------------------------------------------------------- &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从上面的例子中可以看出：当8、9两个数和(15-1)2=(1110)进行“与运算&amp;amp;”的时候，产生了相同的结果，都为0100，也就是说它们会定位到数组中的同一个位置上去，这就产生了碰撞，8和9会被放到数组中的同一个位置上形成链表，那么查询的时候就需要遍历这个链 表，得到8或者9，这样就降低了查询的效率。同时，我们也可以发现，当数组长度为15的时候，hash值会与(15-1)2=(1110)进行“与运算&amp;amp;”，那么最后一位永远是0，而0001，0011，0101，1001，1011，0111，1101这几个位置永远都不能存放元素了，空间浪费相当大，更糟的是这种情况中，数组可以使用的位置比数组长度小了很多，这意味着进一步增加了碰撞的几率，减慢了查询的效率！&lt;/p&gt; &lt;p&gt;　　而当数组长度为16时，即为2的n次方时，2n-1得到的二进制数的每个位上的值都为1（比如(24-1)2=1111），这使得在低位上&amp;amp;时，得到的和原hash的低位相同，加之hash(int h)方法对key的hashCode的进一步优化，加入了高位计算，就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。&lt;/p&gt; &lt;p&gt;　　所以说，当数组长度为2的n次幂的时候，不同的key算得得index相同的几率较小，那么数据在数组上分布就比较均匀，也就是说碰撞的几率小，相对的，查询的时候就不用遍历某个位置上的链表，这样查询效率也就较高了。&lt;/p&gt; &lt;p&gt;　　根据上面 put 方法的源代码可以看出，当程序试图将一个key-value对放入HashMap中时，程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置：如果两个 Entry 的 key 的 hashCode() 返回值相同，那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true，新添加 Entry 的 value 将覆盖集合中原有Entry 的 value，但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false，新添加的 Entry 将与集合中原有 Entry 形成 Entry 链，而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。&lt;/p&gt; &lt;h3&gt;读取：&lt;/h3&gt; &lt;pre&gt;&lt;code&gt; public V get(Object key) {       if (key == null)           return getForNullKey();       int hash = hash(key.hashCode());       for (Entry&amp;lt;K,V&amp;gt; e = table[indexFor(hash, table.length)];           e != null;           e = e.next) {           Object k;           if (e.hash == hash &amp;amp;&amp;amp; ((k = e.key) == key || key.equals(k)))               return e.value;       }       return null;   }    &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;有了上面存储时的hash算法作为基础，理解起来这段代码就很容易了。从上面的源代码中可以看出：从HashMap中get元素时，首先计算key的hashCode，找到数组中对应位置的某一元素，然后通过key的equals方法在对应位置的链表中找到需要的元素。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;归纳起来简单地说，HashMap 在底层将 key-value 当成一个整体进行处理，这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对，当需要存储一个 Entry 对象时，会根据hash算法来决定其在数组中的存储位置，在根据equals方法决定其在该数组位置上的链表中的存储位置；当需要取出一个Entry时，也会根据hash算法找到其在数组中的存储位置，再根据equals方法从该位置上的链表中取出该Entry。&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;HashMap的resize（rehash）：&lt;/h2&gt; &lt;p&gt;当HashMap中的元素越来越多的时候，hash冲突的几率也就越来越高，因为数组的长度是固定的。所以为了提高查询的效率，就要对HashMap的数组进行扩容，数组扩容这个操作也会出现在ArrayList中，这是一个常用的操作，而在HashMap数组扩容之后，最消耗性能的点就出现了：原数组中的数据必须重新计算其在新数组中的位置，并放进去，这就是resize。&lt;/p&gt; &lt;p&gt;　　那么HashMap什么时候进行扩容呢？当HashMap中的元素个数超过数组大小&lt;em&gt;loadFactor时，就会进行数组扩容，loadFactor的默认值为0.75，这是一个折中的取值。也就是说，默认情况下，数组大小为16，那么当HashMap中元素个数超过16&lt;/em&gt;0.75=12（这个值就是代码中的threshold值，也叫做临界值）的时候，就把数组的大小扩展为 2*16=32，即扩大一倍，然后重新计算每个元素在数组中的位置，而这是一个非常消耗性能的操作，所以如果我们已经预知HashMap中元素的个数，那么预设元素的个数能够有效的提高HashMap的性能。&lt;/p&gt; &lt;p&gt;HashMap扩容的代码如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt; //HashMap数组扩容             void resize(int newCapacity) {                   Entry[] oldTable = table;                   int oldCapacity = oldTable.length;                   //如果当前的数组长度已经达到最大值，则不在进行调整                   if (oldCapacity == MAXIMUM_CAPACITY) {                       threshold = Integer.MAX_VALUE;                       return;                   }                   //根据传入参数的长度定义新的数组                   Entry[] newTable = new Entry[newCapacity];                   //按照新的规则，将旧数组中的元素转移到新数组中                   transfer(newTable);                   table = newTable;                   //更新临界值                   threshold = (int)(newCapacity * loadFactor);               }                //旧数组中元素往新数组中迁移               void transfer(Entry[] newTable) {                   //旧数组                   Entry[] src = table;                   //新数组长度                   int newCapacity = newTable.length;                   //遍历旧数组                   for (int j = 0; j &amp;lt; src.length; j++) {                       Entry&amp;lt;K,V&amp;gt; e = src[j];                       if (e != null) {                           src[j] = null;                           do {                               Entry&amp;lt;K,V&amp;gt; next = e.next;                               int i = indexFor(e.hash, newCapacity);                               e.next = newTable[i];                               newTable[i] = e;                               e = next;                           } while (e != null);                       }                   }               }    &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;HashMap的性能参数：&lt;/h2&gt; &lt;p&gt;HashMap 包含如下几个构造器：&lt;/p&gt; &lt;p&gt;HashMap()：构建一个初始容量为 16，负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity)：构建一个初始容量为 initialCapacity，负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity, float loadFactor)：以指定初始容量、指定的负载因子创建一个 HashMap。 HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数，它们是初始容量initialCapacity和加载因子loadFactor。 initialCapacity：HashMap的最大容量，即为底层数组的长度。 loadFactor：负载因子loadFactor定义为：散列表的实际元素数目(n)/ 散列表的容量(m)。 　　负载因子衡量的是一个散列表的空间的使用程度，负载因子越大表示散列表的装填程度越高，反之愈小。对于使用链表法的散列表来说，查找一个元素的平均时间是O(1+a)，因此如果负载因子越大，对空间的利用更充分，然而后果是查找效率的降低；如果负载因子太小，那么散列表的数据将过于稀疏，对空间造成严重浪费。&lt;/p&gt; &lt;p&gt;　　HashMap的实现中，通过threshold字段来判断HashMap的最大容量：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;threshold = (int)(capacity * loadFactor);   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;结合负载因子的定义公式可知，threshold就是在此loadFactor和capacity对应下允许的最大元素数目，超过这个数目就重新resize，以降低实际的负载因子（也就是说虽然数组长度是capacity，但其扩容的临界值确是threshold）。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时， resize后的HashMap容量是容量的两倍：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;if (size++ &amp;gt;= threshold)     resize(2 * table.length);  &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Fail-Fast机制：&lt;/h2&gt; &lt;p&gt;　　我们知道java.util.HashMap不是线程安全的，因此如果在使用迭代器的过程中有其他线程修改了map，那么将抛出ConcurrentModificationException，这就是所谓fail-fast策略。（这个在core java这本书中也有提到。）&lt;/p&gt; &lt;p&gt;　　这一策略在源码中的实现是通过modCount域，modCount顾名思义就是修改次数，对HashMap内容的修改都将增加这个值，那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; HashIterator() {       expectedModCount = modCount;       if (size &amp;gt; 0) { // advance to first entry       Entry[] t = table;       while (index &amp;lt; t.length &amp;amp;&amp;amp; (next = t[index++]) == null)           ;       }   }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在迭代过程中，判断modCount跟expectedModCount是否相等，如果不相等就表示已经有其他线程修改了Map：&lt;/p&gt; &lt;p&gt;　　注意到modCount声明为volatile，保证线程之间修改的可见性。（volatile之所以线程安全是因为被volatile修饰的变量不保存缓存，直接在内存中修改，因此能够保证线程之间修改的可见性）。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; final Entry&amp;lt;K,V&amp;gt; nextEntry() {        if (modCount != expectedModCount)            throw new ConcurrentModificationException();  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在HashMap的API中指出：&lt;/p&gt; &lt;p&gt;　　由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的：在迭代器创建之后，如果从结构上对映射进行修改，除非通过迭代器本身的 remove 方法，其他任何时间任何方式的修改，迭代器都将抛出ConcurrentModificationException。因此，面对并发的修改，迭代器很快就会完全失败，而不保证在将来不确定的时间发生任意不确定行为的风险。&lt;/p&gt; &lt;p&gt;　　注意，迭代器的快速失败行为不能得到保证，一般来说，存在非同步的并发修改时，不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的做法是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测程序错误。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 17 Apr 2018 06:57:00 GMT</pubDate>
    </item>
    <item>
      <title>String转换成Integer源码分析</title>
      <link>https://maruifu.cn/article/76</link>
      <content:encoded>&lt;p&gt;我们经常为用到Integer.valueOf(String str)这个方法,如果字符串格式不对,这个方法会抛出一个系统异常NumberFormatException 这里我们就要分析一下这个方法,其中Byte,Short也是调用了Ingeter中的方法. 在Integer类中的定义如下:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public static Integer valueOf(String s) throws NumberFormatException     {  return new Integer(parseInt(s, 10));     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里因为parseInt方法返回的int型的,这里调用了一个构造函数产生了一个新的Integer实例. 这里关心的是parseInt方法,该方法代码如下:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public static int parseInt(String s, int radix)   throws NumberFormatException     {         if (s == null) {             throw new NumberFormatException(&amp;quot;null&amp;quot;);         }   if (radix &amp;lt; Character.MIN_RADIX) {      throw new NumberFormatException(&amp;quot;radix &amp;quot; + radix +          &amp;quot; less than Character.MIN_RADIX&amp;quot;);  }   if (radix &amp;gt; Character.MAX_RADIX) {      throw new NumberFormatException(&amp;quot;radix &amp;quot; + radix +          &amp;quot; greater than Character.MAX_RADIX&amp;quot;);  }   int result = 0;  boolean negative = false;  int i = 0, max = s.length();  int limit;  int multmin;  int digit;   if (max &amp;gt; 0) {      if (s.charAt(0) == '-') {   negative = true;   limit = Integer.MIN_VALUE;   i++;      } else {   limit = -Integer.MAX_VALUE;      }       if (i &amp;lt; max) {   digit = Character.digit(s.charAt(i++),radix);   if (digit &amp;lt; 0) {       throw NumberFormatException.forInputString(s);   } else {       result = -digit;   }      }      while (i &amp;lt; max) {   // Accumulating negatively avoids surprises near MAX_VALUE   digit = Character.digit(s.charAt(i++),radix);   if (digit &amp;lt; 0) {       throw NumberFormatException.forInputString(s);    }   if (result &amp;lt; multmin) {       throw NumberFormatException.forInputString(s);  异常1   }   result *= radix;   if (result &amp;lt; limit + digit) {       throw NumberFormatException.forInputString(s);  异常2   }   result -= digit;      }  } else {      throw NumberFormatException.forInputString(s);  }   if (negative) {      if (i &amp;gt; 1) {   return result;      } else { /* Only got &amp;quot;-&amp;quot; */   throw NumberFormatException.forInputString(s);      }  } else {      return -result;  }     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;很显然,该方法的第二个参数表示是基数(最常用的是十进制,还有十六机制,八进制等等).&lt;/p&gt; &lt;p&gt;如果字符串是空指针,直接抛出异常.&lt;/p&gt; &lt;p&gt;如果基础小于2或者大于36的话,抛出异常(这种情况一般不会出现,因为我们用的最多就是十进制的了).&lt;/p&gt; &lt;p&gt;如果是空字符串,也抛出异常,也就是max=0的情况了.&lt;/p&gt; &lt;p&gt;我们来关注下面的转换过程:&lt;/p&gt; &lt;p&gt;这里使用了Character中的静态方法digit,这个方法比较复杂,这里先说明它的功能:对于给定的基数,如果是合法的字符(可以转化为数字),返回该数字值,否则返回-1.比如digit('3',10)返回3,digit('a',10)返回-1.&lt;/p&gt; &lt;p&gt;这段程序看起来很简单,其实还真不容易看懂,这里先说明几个局部变量的含义吧:&lt;/p&gt; &lt;p&gt;result:记录返回值&lt;/p&gt; &lt;p&gt;negative:符号标志&lt;/p&gt; &lt;p&gt;i:字符串位置&lt;/p&gt; &lt;p&gt;s:字符串长度&lt;/p&gt; &lt;p&gt;limit:界限&lt;/p&gt; &lt;p&gt;multmin:也是一个界限&lt;/p&gt; &lt;p&gt;digit:当前字符表示的数字&lt;/p&gt; &lt;p&gt;先看第一个字符是否是'-'号,设定符号标志negative和极限值limit.&lt;/p&gt; &lt;p&gt;注意到limit一定是一个负值.&lt;/p&gt; &lt;p&gt;处理最高位,这里result保存的是负值,这样就可以对正负数统一处理.&lt;/p&gt; &lt;p&gt;关键就是这个while循环了,第一个if不用解释了,肯定是因为非法字符.&lt;/p&gt; &lt;p&gt;第二个if语句的含义:如果result小于multmin,会产生什么结果呢?&lt;/p&gt; &lt;p&gt;是不是一定会溢出呢?假设不会溢出,就是说结果必须&amp;gt;=limit.&lt;/p&gt; &lt;p&gt;result小于multmin,result至少应该位multmin-1,后面有 result=result*radix=(multmin-1)&lt;em&gt;radix=multmin&lt;/em&gt;radix-radix&lt;/p&gt; &lt;p&gt;该值肯定小于limit,其中multmin=limit/radix,注意这里都是负数.&lt;/p&gt; &lt;p&gt;所以假设不成里,如果result小于multmin的话,后面一定会溢出.&lt;/p&gt; &lt;p&gt;如果这里没有判断的话,溢出就麻烦了,正数也会变负数了.&lt;/p&gt; &lt;p&gt;第三个if语句的含义:在这条语句以前肯定没有溢出,但是有可能加上最后一位digit就溢出了,所以这个判断也是必要的.&lt;/p&gt; &lt;p&gt;后面的就比较好理解了,else是表示空字符串&amp;quot;&amp;quot;.&lt;/p&gt; &lt;p&gt;如果是负数的还要看是否长度是1,就只是一个'-'号的情况.&lt;/p&gt; &lt;p&gt;如果是正数的话返回相反数就可以了.&lt;/p&gt; &lt;p&gt;这里有好多地方都有可能抛出异常,只要看明白了程序就知道这个异常是 那条语句抛出的了,这里考虑溢出异常:异常1和异常2.&lt;/p&gt; &lt;p&gt;Ingeter.Max_VALUE=2147483647&lt;/p&gt; &lt;p&gt;下面的两条语句在不同的地方抛出异常.&lt;/p&gt; &lt;p&gt;Ingeter.valueOf(&amp;quot;2147483648&amp;quot;);这个在异常2抛出的.&lt;/p&gt; &lt;p&gt;Ingeter.valueOf(&amp;quot;21474836471&amp;quot;);这个在异常1抛出的.&lt;/p&gt; &lt;p&gt;这里简单的分析了String转化为Ingeter的过程,其实整个Ingeter类也就主要是这个方法了,Byte和Short都是调用这个方法的. 看看Byte的代码:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public static byte parseByte(String s, int radix)  throws NumberFormatException {  int i = Integer.parseInt(s, radix);  if (i &amp;lt; MIN_VALUE || i &amp;gt; MAX_VALUE)      throw new NumberFormatException(                 &amp;quot;Value out of range. Value:/&amp;quot;&amp;quot; + s + &amp;quot;/&amp;quot; Radix:&amp;quot; + radix);  return (byte)i;    } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;了解这个方法后就再也不会为Integer.valueOf()产生的异常感到意外了,特别是在JSP中,因为参数都是String型的,转换的时候动不动就出现异&lt;/p&gt; &lt;p&gt;常,你该知道怎么回事了吧.&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 22 Mar 2018 08:29:26 GMT</pubDate>
    </item>
    <item>
      <title>JVM运行原理详解</title>
      <link>https://maruifu.cn/article/75</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;作为一名Java使用者，掌握JVM的体系结构也是很有必要的&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;1.JVM简析：&lt;/h2&gt; &lt;p&gt;说起Java，我们首先想到的是Java编程语言，然而事实上，Java是一种技术，它由四方面组成：Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM1.png" alt="Java组成关系" title="Java组成关系" /&gt;&lt;/p&gt; &lt;p&gt;Java平台由Java虚拟机和Java应用程序接口搭建，Java语言则是进入这个平台的通道，用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示：     运行期环境代表着Java平台，开发人员编写Java代码(.java文件)，然后将之编译成字节码(.class文件)，再然后字节码被装入内存，一旦字节码进入虚拟机，它就会被解释器解释执行，或者是被即时代码发生器有选择的转换成机器码执行。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM2.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;JVM在它的生存周期中有一个明确的任务，那就是运行Java程序，因此当Java程序启动的时候，就产生JVM的一个实例；当程序运行结束的时候，该实例也跟着消失了。     在Java平台的结构中, 可以看出，Java虚拟机(JVM) 处在核心的位置，是程序与底层操作系统和硬件无关的关键。它的下方是移植接口，移植接口由两部分组成：适配器和Java操作系统, 其中依赖于平台的部分称为适配器；JVM 通过移植接口在具体的平台和操作系统上实现；在JVM 的上方是Java的基本类库和扩展类库以及它们的API， 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离，从而实现了Java 的平台无关性。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;下面我们从JVM的基本概念和运过程程这两个方面入手来对它进行深入的研究。*&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;2.JVM基本概念&lt;/h2&gt; &lt;h3&gt;(1) 基本概念：&lt;/h3&gt; &lt;p&gt;JVM是可运行Java代码的假想计算机 ，包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收，堆 和 一个存储方法域。JVM是运行在操作系统之上的，它与硬件没有直接的交互。&lt;/p&gt; &lt;h3&gt;(2) 运行过程：&lt;/h3&gt; &lt;p&gt;我们都知道Java源文件，通过编译器，能够生产相应的.Class文件，也就是字节码文件，而字节码文件又通过Java虚拟机中的解释器，编译成特定机器上的机器码 。&lt;/p&gt; &lt;p&gt;也就是如下：&lt;/p&gt; &lt;p&gt;① Java源文件—-&amp;gt;编译器—-&amp;gt;字节码文件&lt;/p&gt; &lt;p&gt;② 字节码文件—-&amp;gt;JVM—-&amp;gt;机器码&lt;/p&gt; &lt;p&gt;每一种平台的解释器是不同的，但是实现的虚拟机是相同的，这也就是Java为什么能够跨平台的原因了 ，当一个程序从开始运行，这时虚拟机就开始实例化了，多个程序启动就会存在多个虚拟机实例。程序退出或者关闭，则虚拟机实例消亡，多个虚拟机实例之间数据不能共享。&lt;/p&gt; &lt;h4&gt;类加载详细过程&lt;/h4&gt; &lt;p&gt;一个Java文件从编码完成到最终执行，一般主要包括两个过程编译和运行&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;编译:把我们写好的java文件，通过javac命令编译成字节码，也就是我们常说的.class文件。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;运行:是把编译生成的.class文件交给Java虚拟机(JVM)执行。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;而我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存，并进行解析生成对应的class对象的过程。&lt;/p&gt; &lt;p&gt;举个通俗点的例子来说，JVM在执行某段代码时，遇到了class A， 然而此时内存中并没有class A的相关信息，于是JVM就会到相应的class文件中去寻找class A的类信息，并加载进内存中，这就是我们所说的类加载过程。&lt;/p&gt; &lt;p&gt;由此可见，JVM不是一开始就把所有的类都加载进内存中，而是只有第一次遇到某个需要运行的类时才会加载，且&lt;strong&gt;只加载一次&lt;/strong&gt;。&lt;/p&gt; &lt;h4&gt;类加载&lt;/h4&gt; &lt;p&gt;类加载的过程主要分为三个部分：”加载”,””链接”,”“初始化”&lt;/p&gt; &lt;p&gt;而链接又可以细分为三个小部分：”验证”,”准备”,”解析”&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/2021-12-19-6.45.24.png" alt="截屏2021-12-19 下午6.45.24" title="截屏2021-12-19 下午6.45.24" /&gt;&lt;/p&gt; &lt;h5&gt;加载&lt;/h5&gt; &lt;p&gt;简单来说，加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。&lt;/p&gt; &lt;p&gt;这里有两个重点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;字节码来源&lt;/strong&gt;。一般的加载来源包括从本地路径下编译生成的.class文件，从jar包中的.class文件，从远程网络，以及动态代理实时编译&lt;/li&gt; &lt;li&gt;&lt;strong&gt;类加载器&lt;/strong&gt;。一般包括&lt;strong&gt;启动类加载器&lt;/strong&gt;，&lt;strong&gt;扩展类加载器&lt;/strong&gt;，&lt;strong&gt;应用类加载器&lt;/strong&gt;，以及用户的&lt;strong&gt;自定义类加载器&lt;/strong&gt;。&lt;/li&gt; &lt;/ul&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;为什么会有自定义类加载器？&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;一方面是由于java代码很容易被反编译，如果需要对自己的代码加密的话，可以对编译后的代码进行加密，然后再通过实现自己的自定义类加载器进行解密，最后再加载。&lt;/li&gt; &lt;li&gt;另一方面也有可能从非标准的来源加载代码，比如从网络来源，那就需要自己实现一个类加载器，从指定源进行加载。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;h5&gt;链接&lt;/h5&gt; &lt;h6&gt;验证&lt;/h6&gt; &lt;p&gt;主要是为了保证加载进来的字节流符合虚拟机规范，不会造成安全错误。&lt;/p&gt; &lt;p&gt;包括对于&lt;strong&gt;文件格式的验证&lt;/strong&gt;，比如常量中是否有不被支持的常量？文件中是否有不规范的或者附加的其他信息？&lt;/p&gt; &lt;p&gt;对于&lt;strong&gt;元数据的验证&lt;/strong&gt;，比如该类是否继承了被final修饰的类？类中的字段，方法是否与父类冲突？是否出现了不合理的重载？&lt;/p&gt; &lt;p&gt;对于&lt;strong&gt;字节码的验证&lt;/strong&gt;，保证程序语义的合理性，比如要保证类型转换的合理性。&lt;/p&gt; &lt;p&gt;对于&lt;strong&gt;符号引用的验证&lt;/strong&gt;，比如校验符号引用中通过全限定名是否能够找到对应的类？校验符号引用中的访问性（private，public等）是否可被当前类访问？&lt;/p&gt; &lt;h6&gt;准备&lt;/h6&gt; &lt;p&gt;主要是为类变量（注意，不是实例变量）分配内存，并且赋予&lt;strong&gt;初值&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;特别需要注意，&lt;strong&gt;初值，不是代码中具体写的初始化的值&lt;/strong&gt;，而是Java虚拟机根据不同变量类型的默认初始值。&lt;/p&gt; &lt;p&gt;比如8种&lt;strong&gt;基本类型&lt;/strong&gt;的初值，默认为0；&lt;strong&gt;引用类型&lt;/strong&gt;的初值则为null；&lt;strong&gt;常量&lt;/strong&gt;的初值即为代码中设置的值，final static tmp = 456， 那么该阶段tmp的初值就是456&lt;/p&gt; &lt;h6&gt;解析&lt;/h6&gt; &lt;p&gt;将常量池内的符号引用替换为直接引用的过程。&lt;/p&gt; &lt;p&gt;两个重点：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;符号引用&lt;/strong&gt;。即一个字符串，但是这个字符串给出了一些能够唯一性识别一个方法，一个变量，一个类的相关信息。&lt;/li&gt; &lt;li&gt;&lt;strong&gt;直接引用&lt;/strong&gt;。可以理解为一个内存地址，或者一个偏移量。比如&lt;strong&gt;类方法，类变量&lt;/strong&gt;的直接引用是指向方法区的&lt;strong&gt;指针&lt;/strong&gt;；而&lt;strong&gt;实例方法，实例变量&lt;/strong&gt;的直接引用则是从实例的头指针开始算起到这个实例变量位置的&lt;strong&gt;偏移量&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;举个例子来说，现在调用方法hello()，这个方法的地址是1234567，那么hello就是符号引用，1234567就是直接引用。&lt;/p&gt; &lt;p&gt;在解析阶段，虚拟机会把所有的类名，方法名，字段名这些符号引用替换为具体的内存地址或偏移量，也就是直接引用。&lt;/p&gt; &lt;h5&gt;初始化&lt;/h5&gt; &lt;p&gt;这个阶段主要是对&lt;strong&gt;类变量&lt;/strong&gt;初始化，是执行类构造器的过程。&lt;/p&gt; &lt;p&gt;换句话说，只对static修饰的变量或语句进行初始化。&lt;/p&gt; &lt;p&gt;如果初始化一个类的时候，其父类尚未初始化，则优先初始化其父类。&lt;/p&gt; &lt;p&gt;如果同时包含多个静态变量和静态代码块，则按照自上而下的顺序依次执行。&lt;/p&gt; &lt;h4&gt;总结&lt;/h4&gt; &lt;p&gt;类加载过程只是一个类生命周期的一部分，在其前，有编译的过程，只有对源代码编译之后，才能获得能够被虚拟机加载的字节码文件；在其后还有具体的类使用过程，当使用完成之后，还会在方法区垃圾回收的过程中进行卸载。如果想要了解Java类整个生命周期的话，可以自行上网查阅相关资料，这里不再多做赘述。&lt;/p&gt; &lt;h3&gt;(3) 三种JVM:&lt;/h3&gt; &lt;p&gt;① Sun公司的HotSpot；&lt;/p&gt; &lt;p&gt;② BEA公司的JRockit；&lt;/p&gt; &lt;p&gt;③ IBM公司的J9 JVM；&lt;/p&gt; &lt;p&gt;在JDK1.7及其以前我们所使用的都是Sun公司的HotSpot，但由于Sun公司和BEA公司都被oracle收购，jdk1.8将采用Sun公司的HotSpot和BEA公司的JRockit两个JVM中精华形成jdk1.8的JVM。&lt;/p&gt; &lt;h2&gt;3.JVM的体系结构&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h3&gt;(1) Class Loader类加载器&lt;/h3&gt; &lt;p&gt;负责加载 .class文件，class文件在文件开头有特定的文件标示，并且ClassLoader负责class文件的加载等，至于它是否可以运行，则由Execution Engine决定。 ① 定位和导入二进制class文件 ② 验证导入类的正确性 ③ 为类分配初始化内存 ④ 帮助解析符号引用.&lt;/p&gt; &lt;h3&gt;(2) Native Interface本地接口:&lt;/h3&gt; &lt;p&gt;本地接口的作用是融合不同的编程语言为Java所用，它的初衷是融合C/C++程序，Java诞生的时候C/C++横行的时候，要想立足，必须有调用C/C++程序，于是就在内存中专门开辟了一块区域处理标记为native的代码，它的具体作法是Native Method Stack中登记native方法，在Execution Engine执行时加载native libraies。 目前该方法使用的越来越少了，除非是与硬件有关的应用，比如通过Java程序驱动打印机，或者Java系统管理生产设备，在企业级应用中已经比较少见。 因为现在的异构领域间的通信很发达，比如可以使用Socket通信，也可以使用Web Service等。&lt;/p&gt; &lt;h3&gt;(3) Execution Engine 执行引擎：&lt;/h3&gt; &lt;p&gt;执行包在装载类的方法中的指令，也就是方法。&lt;/p&gt; &lt;h3&gt;(4) Runtime data area 运行数据区:&lt;/h3&gt; &lt;p&gt;虚拟机内存或者Jvm内存，冲整个计算机内存中开辟一块内存存储Jvm需要用到的对象，变量等，运行区数据有分很多小区，分别为：方法区，虚拟机栈，本地方法栈，堆，程序计数器。&lt;/p&gt; &lt;h2&gt;4.JVM数据运行区详解（栈管运行，堆管存储）：&lt;/h2&gt; &lt;p&gt;说明：JVM调优主要就是优化 Heap堆 和 Method Area 方法区。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM4.png" alt="JVM运行时数据区域" title="JVM运行时数据区域" /&gt;&lt;/p&gt; &lt;h3&gt;(1) Native Method Stack本地方法栈&lt;/h3&gt; &lt;p&gt;它的具体做法是Native Method Stack中登记native方法，在Execution Engine执行时加载native libraies。&lt;/p&gt; &lt;h3&gt;(2) PC Register程序计数器&lt;/h3&gt; &lt;p&gt;每个线程都有一个程序计算器，就是一个指针，指向方法区中的方法字节码（下一个将要执行的指令代码），由执行引擎读取下一条指令，是一个非常小的内存空间，几乎可以忽略不记。&lt;/p&gt; &lt;h3&gt;(3) Method Area方法区&lt;/h3&gt; &lt;p&gt;方法区是被所有线程共享，所有字段和方法字节码，以及一些特殊方法如构造函数，接口代码也在此定义。简单说，所有定义的方法的信息都保存在该区域，此区域属于共享区间。 静态变量+常量+类信息+运行时常量池存在方法区中，实例变量存在堆内存中。&lt;/p&gt; &lt;h2&gt;5 Stack 栈&lt;/h2&gt; &lt;h3&gt;① 栈是什么&lt;/h3&gt; &lt;p&gt;栈也叫栈内存，主管Java程序的运行，是在线程创建时创建，它的生命期是跟随线程的生命期，线程结束栈内存也就释放，对于栈来说不存在垃圾回收问题，只要线程一结束该栈就Over，生命周期和线程一致，是线程私有的。 基本类型的变量和对象的引用变量都是在函数的栈内存中分配。&lt;/p&gt; &lt;h3&gt;② 栈存储什么？&lt;/h3&gt; &lt;p&gt;栈帧中主要保存3类数据： 本地变量（Local Variables）：输入参数和输出参数以及方法内的变量； 栈操作（Operand Stack）：记录出栈、入栈的操作； 栈帧数据（Frame Data）：包括类文件、方法等等。&lt;/p&gt; &lt;h3&gt;③ 栈运行原理&lt;/h3&gt; &lt;p&gt;栈中的数据都是以栈帧（Stack Frame）的格式存在，栈帧是一个内存区块，是一个数据集，是一个有关方法和运行期数据的数据集，当一个方法A被调用时就产生了一个栈帧F1，并被压入到栈中，A方法又调用了B方法，于是产生栈帧F2也被压入栈，B方法又调用了C方法，于是产生栈帧F3也被压入栈…… 依次执行完毕后，先弹出后进......F3栈帧，再弹出F2栈帧，再弹出F1栈帧。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;遵循“先进后出”/“后进先出”原则。&lt;/strong&gt;&lt;/p&gt; &lt;h2&gt;6 Heap 堆&lt;/h2&gt; &lt;p&gt;堆这块区域是JVM中最大的，应用的对象和数据都是存在这个区域，这块区域也是线程共享的，也是 gc 主要的回收区，一个 JVM 实例只存在一个堆类存，堆内存的大小是可以调节的。类加载器读取了类文件后，需要把类、方法、常变量放到堆内存中，以方便执行器执行，堆内存分为三部分：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM5.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h3&gt;① 新生区&lt;/h3&gt; &lt;p&gt;新生区是类的诞生、成长、消亡的区域，一个类在这里产生，应用，最后被垃圾回收器收集，结束生命。新生区又分为两部分：伊甸区（Eden space）和幸存者区（Survivor pace），所有的类都是在伊甸区被new出来的。幸存区有两个：0区（Survivor 0 space）和1区（Survivor 1 space）。当伊甸园的空间用完时，程序又需要创建对象，JVM的垃圾回收器将对伊甸园进行垃圾回收（Minor GC）,将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了，再对该区进行垃圾回收，然后移动到1区。那如果1去也满了呢？再移动到养老区。若养老区也满了，那么这个时候将产生Major GC（FullGCC），进行养老区的内存清理。若养老区执行Full GC 之后发现依然无法进行对象的保存，就会产生OOM异常“OutOfMemoryError”。&lt;/p&gt; &lt;p&gt;如果出现java.lang.OutOfMemoryError: Java heap space异常，说明Java虚拟机的堆内存不够。原因有二：&lt;/p&gt; &lt;p&gt;a.Java虚拟机的堆内存设置不够，可以通过参数-Xms、-Xmx来调整。&lt;/p&gt; &lt;p&gt;b.代码中创建了大量大对象，并且长时间不能被垃圾收集器收集（存在被引用）。&lt;/p&gt; &lt;h3&gt;② 养老区&lt;/h3&gt; &lt;p&gt;养老区用于保存从新生区筛选出来的 JAVA 对象，一般池对象都在这个区域活跃。&lt;/p&gt; &lt;h3&gt;③ 永久区&lt;/h3&gt; &lt;p&gt;永久存储区是一个常驻内存区域，用于存放JDK自身所携带的 Class,Interface 的元数据，也就是说它存储的是运行环境必须的类信息，被装载进此区域的数据是不会被垃圾回收器回收掉的，关闭 JVM 才会释放此区域所占用的内存。&lt;/p&gt; &lt;p&gt;如果出现java.lang.OutOfMemoryError: PermGen space，说明是Java虚拟机对永久代Perm内存设置不够。原因有二：&lt;/p&gt; &lt;p&gt;a. 程序启动需要加载大量的第三方jar包。例如：在一个Tomcat下部署了太多的应用。 b. 大量动态反射生成的类不断被加载，最终导致Perm区被占满。&lt;/p&gt; &lt;p&gt;说明： Jdk1.6及之前：常量池分配在永久代 。 Jdk1.7：有，但已经逐步“去永久代” 。 Jdk1.8及之后：无(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM6.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;说明：方法区和堆内存的异议： 实际而言，方法区和堆一样，是各个线程共享的内存区域，它用于存储虚拟机加载的：类信息+普通常量+静态常量+编译器编译后的代码等等，虽然JVM规范将方法区描述为堆的一个逻辑部分，但它却还有一个别名叫做Non-Heap（非堆），目的就是要和堆分开。&lt;/p&gt; &lt;p&gt;对于HotSpot虚拟机，很多开发者习惯将方法区称之为“永久代（Parmanent Gen）”,但严格本质上说两者不同，或者说使用永久代来实现方法区而已，永久代是方法区的一个实现，jdk1.7的版本中，已经将原本放在永久代的字符串常量池移走。&lt;/p&gt; &lt;p&gt;常量池（Constant Pool）是方法区的一部分，Class文件除了有类的版本、字段、方法、接口等描述信息外，还有一项信息就是常量池，这部分内容将在类加载后进入方法区的运行时常量池中存放。&lt;/p&gt; &lt;p&gt;6.堆内存调优简介&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM7.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;代码测试：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class JVMTest {        public static void main(String[] args){             long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机试图使用的最大内存量。             Long totalMemory = Runtime. getRuntime().totalMemory();//返回Java虚拟机中的内存总量。             System.out.println(&amp;quot;MAX_MEMORY =&amp;quot;+maxMemory +&amp;quot;(字节)、&amp;quot;+(maxMemory/(double)1024/1024) + &amp;quot;MB&amp;quot;);             System.out.println(&amp;quot;TOTAL_ MEMORY = &amp;quot;+totalMemory +&amp;quot;(字节)&amp;quot;+(totalMemory/(double)1024/1024) + &amp;quot;MB&amp;quot;);        }   }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;说明：在Run as -&amp;gt;Run Configurations中输入&amp;quot;-XX:+PrintGCDetails&amp;quot;可以查看堆内存运行原理图：&lt;/p&gt; &lt;p&gt;(1) 在jdk1.7中：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM8.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;(2) 在jdk1.8中:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM9.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h2&gt;7.通过参数设置自动触发垃圾回收：&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;public class JVMTest {      public static void main(String[] args){           long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机试图使用的最大内存量。           Long totalMemory = Runtime. getRuntime().totalMemory();//返回Java虚拟机中的内存总量。           System.out.println(&amp;quot;MAX_MEMORY =&amp;quot;+maxMemory +&amp;quot;(字节)、&amp;quot;+(maxMemory/(double)1024/1024) + &amp;quot;MB&amp;quot;);           System.out.println(&amp;quot;TOTAL_ MEMORY = &amp;quot;+totalMemory +&amp;quot;(字节)&amp;quot;+(totalMemory/(double)1024/1024) + &amp;quot;MB&amp;quot;);           String str = &amp;quot;www.baidu.com&amp;quot;;           while(true){               str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);           }      } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在Run as -&amp;gt;Run Configurations中输入设置“-Xmx8m –Xms8m –xx:+PrintGCDetails”可以参看垃圾回收机制原理:&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/JVM10.png" alt="" title="" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 21 Mar 2018 01:20:00 GMT</pubDate>
    </item>
    <item>
      <title>阅读源码的三种境界</title>
      <link>https://maruifu.cn/article/74</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;&amp;quot;没有经验的技术差底子薄的初级程序员，如何阅读项目源码？ &amp;quot;&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;p&gt;&amp;quot;有人阅读过 mybatis 的源码吗 ？就看一个初始化过程就看的已经头晕眼花了，小伙伴们支支招吧！&amp;quot;&lt;/p&gt; &lt;p&gt;&amp;quot;源码应该怎么阅读，我曾经尝试阅读一些源码，例如alibaba的druid中sqlparser部分，spring-mvc，但是发现很吃力，都说debug是最好的阅读方式，我在debug时经常有跟丢的现象……就是走着走着感觉好像进入了一些我当前不太关注细枝末节。 &amp;quot; 。。。。。。&lt;/p&gt; &lt;p&gt;估计很多人都有这样的疑惑。&lt;/p&gt; &lt;p&gt;我非常能理解小伙伴们的痛苦，因为我也是这么痛苦着走过来的。&lt;/p&gt; &lt;p&gt;阅读优秀源码的好处想必大家都知道，学习别人优秀的设计，合理的抽象，简洁的代码...... 总之是好处多多。&lt;/p&gt; &lt;p&gt;但是真的把庞大的代码放到你的面前，就如同一个巨大的迷宫，要在其中东转西转寻出一条路来，把迷宫的整个结构搞清楚，理解核心思想，真心不容易。&lt;/p&gt; &lt;p&gt;在阅读由面向对象的语言如Java写的代码时，会发现接口和具体的实现经常对应不起来，不太清楚一个功能到底是怎么在哪个实现类中才能找到。  不像C语言，就是函数调用函数，相对还好点。&lt;/p&gt; &lt;p&gt;如果是动态语言如Ruby,Python， 一个变量的类型甚至都不容易知道，阅读的难度大大增加。&lt;/p&gt; &lt;p&gt;还有一个重要的原因，现在我们看到的源码基本上都经过若干年发展、经过很多人不断地完善的，枝枝蔓蔓非常多，魔鬼都在细节中。  阅读的时候很容易陷进去， 看了几十层函数调用以后，就彻底懵了，就放弃了： 甭管你把源码吹得天花乱坠， 老子再也不看了。&lt;/p&gt; &lt;p&gt;经过很多痛苦的挣扎以后，我也算有一些成功的经历，今天用治学的三个境界来类比， 给大家分享一下：&lt;/p&gt; &lt;h2&gt;昨夜西风凋碧树，独上高楼，望尽天涯路&lt;/h2&gt; &lt;p&gt;想把源码搞懂，吃透，首先得登高望远，瞰察路径，明确目标与方向，了解源码的概貌。&lt;/p&gt; &lt;p&gt;所以有些准备工作必须得做。&lt;/p&gt; &lt;h3&gt;阅读源码之前，需要有一定的技术储备。&lt;/h3&gt; &lt;p&gt;比如设计模式，在很多Java源码中几乎就是标配，尤其是这几个：模板方法，单例，观察者，工厂方法，代理，策略，装饰者。&lt;/p&gt; &lt;p&gt;再比如阅读Spring源码，肯定得先了解IoC是怎么回事，AOP的实现方式，CGLib，Java动态代理等，自己动手，写点相关的代码，把这些知识点掌握了。&lt;/p&gt; &lt;h3&gt;必须得会使用这个框架/类库， 最好是精通各种各样的用法。&lt;/h3&gt; &lt;p&gt;上面刚提过，魔鬼都在细节中，如果有些用法根本不知道，可能你能看明白代码是什么意思，但是不知道它为什么这些写。&lt;/p&gt; &lt;h3&gt;先去找书，找资料，了解这个软件的整体设计。&lt;/h3&gt; &lt;p&gt;都有哪些模块？ 模块之间是怎么关联的？怎么关联的？ 可能一下子理解不了，但是要建立一个整体的概念，就像一个地图，防止你迷航。&lt;/p&gt; &lt;p&gt;在读源码的时候可以时不时看看自己在什么地方。&lt;/p&gt; &lt;h3&gt;搭建系统，把源代码跑起来！&lt;/h3&gt; &lt;p&gt;相信我，Debug是非常非常重要的手段， 你想通过只看而不运行就把系统搞清楚，那是根本不可能的！&lt;/p&gt; &lt;h2&gt;衣带渐宽终不悔，为伊消得人憔悴。&lt;/h2&gt; &lt;h3&gt;根据你对系统的理解，设计几个主要的测试案例，定义好输入，输出 ###。&lt;/h3&gt; &lt;p&gt;运行系统，慢慢地debug ，一步步地走，这是个死功夫，没有办法绕过。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Debug一遍肯定是不行的，需要Debug很多遍。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;第一遍尽可能抛弃细节，抓住主要流程， 比如有些看起来不重要的方法就不进去看了。&lt;/p&gt; &lt;p&gt;第二遍、第三遍....再去看那些细节。&lt;/p&gt; &lt;p&gt;一个非常重要的工作就是记笔记（又是写作！），画出系统的类图（不要依靠IDE给你生成的）， 记录下主要的函数调用， 方便后续查看。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;文档工作极为重要&lt;/strong&gt;，因为代码太复杂，人的大脑容量也有限，记不住所有的细节。 文档可以帮助你记住关键点， 到时候可以回想起来，迅速地接着往下看。&lt;/p&gt; &lt;p&gt;要不然，你今天看的，可能到明天就忘个差不多了。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;看源码 最好 画个流程图&lt;/strong&gt;。&lt;/p&gt; &lt;h3&gt;主要的测试案例搞明白了，丰富测试案例，考虑一些分支流程。&lt;/h3&gt; &lt;p&gt;继续Debug......&lt;/p&gt; &lt;p&gt;总之，静态地看代码 + 动态地debug (从业务的角度)， 就会慢慢揭开这个黑暗森林的面纱。&lt;/p&gt; &lt;p&gt;这一步会非常非常地花费时间，但是你做完了，对系统的理解绝对有质的飞跃。&lt;/p&gt; &lt;h2&gt;众里寻他千百度，蓦然回首，那人却在灯火阑珊处。&lt;/h2&gt; &lt;p&gt;没有千百度的上下求索，不会有瞬间的顿悟和理解，衷心祝愿阅读源码的朋友们都能达到这一境界。&lt;/p&gt; &lt;p&gt;最后一点，也是最关键的一点： 要能坚持下去。&lt;/p&gt; &lt;p&gt;我不是一个聪明人， 但是笨人自有笨办法：什么事都架不住不断的重复，一遍看不明白，再来第二遍， 两遍搞不明白，再来第三遍......&lt;/p&gt; &lt;p&gt;可能有人要问： 你怎么能这么坚持地刨根问底呢？&lt;/p&gt; &lt;p&gt;答案就是好奇心： 这玩意儿到底是怎么实现的？！的？！&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 15 Mar 2018 02:15:17 GMT</pubDate>
    </item>
    <item>
      <title>如何理解并掌握 Java 数据结构</title>
      <link>https://maruifu.cn/article/73</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;身为一个程序员 看到这个公式  &lt;code&gt;程序 = 数据结构 + 算法&lt;/code&gt;  ，数据结构的重要性不言而喻了吧！&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;第一部分：Java 数据结构&lt;/h2&gt; &lt;p&gt;要理解Java数据结构，必须能清楚何为数据结构？&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;数据结构:&lt;/p&gt; &lt;/blockquote&gt; &lt;ol&gt; &lt;li&gt;Data_Structure，它是储存数据的一种结构体，在此结构中储存一些数据，而这些数据之间有一定的关系。&lt;/li&gt; &lt;li&gt;而各数据元素之间的相互关系，又包括三个组成成分，数据的逻辑结构，数据的存储结构和数据运算结构。&lt;/li&gt; &lt;li&gt;而一个数据结构的设计过程分成抽象层、数据结构层和实现层。 数据结构在Java的语言体系中按逻辑结构可以分为两大类：线性数据结构和非线性数据结构。&lt;/li&gt; &lt;/ol&gt; &lt;hr /&gt; &lt;h3&gt;一、Java数据结构之：线性数据结构&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;线性数据结构：常见的有一维数组，线性表，栈，队列，双队列，串。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;一维数组&lt;/h4&gt; &lt;p&gt;在Java里面常用的util有：String [],int [],ArrayList,Vector,CopyOnWriteArrayList等。及可以同过一维数组[]自己实现不同逻辑结构的Util类。而ArrayList封装了一些[]的基本操作方法。ArrayList和Vector的区别是:Vector是线程安全的，方法同步。CopyOnWriteArrayList也是线程安全的但效率要比Vector高很多。&lt;/p&gt; &lt;p&gt;数组这种数据结构典型的操作方法，是根据下标进行操作的，所以insert的的时候可以根据下标插入到具体的某个位置，但是这个时候它后面的元素都得往后面移动一位。所以插入效率比较低,更新，删除效率也比较低，而查询效率非常高,查询效率时间复杂度是1。&lt;/p&gt; &lt;h4&gt;线性表&lt;/h4&gt; &lt;p&gt;线性表是有序的储存结构、链式的储存结构。链表的物理储存空间是不连续的，链表的每一个节点都知道上一个节点、或者下一个节点是谁，通常用Node表示。常见的有顺序链表(LinkedList、Linked***)，单项链表（里面只有Node类），双向链表(两个Node类)，循环链表(多个Node类)等。&lt;/p&gt; &lt;p&gt;操作方法：插入效率比较高，插入的时候只需要改变节点的前后节点的连接即可。而查询效率就比较低了，如果实现的不好，需要整个链路找下去才能找到应该找的元素。所以常见的方法有：add(index,element),addFirst(element),addLast(element)。getFirst(),getLast(),get(element)等。&lt;/p&gt; &lt;p&gt;常见的Uitil有：LinkedList，LinkedMap等，而这两个JDK底层也做了N多优化，可以有效避免查询效率低的问题。当自己实现的时候需要注意。其实树形结构可以说是非线性的链式储存结构。&lt;/p&gt; &lt;h4&gt;栈Stack&lt;/h4&gt; &lt;p&gt;栈,最主要的是要实现先进后出，后进先出的逻辑结构。来实现一些场景对逻辑顺序的要求。所以常用的方法有push(element)压栈，pop()出栈。&lt;/p&gt; &lt;p&gt;java.util.Stack。就实现了这用逻辑。而Java的Jvm里面也用的到了此种数据结构，就是线程栈，来保证当前线程的执行顺序。&lt;/p&gt; &lt;h4&gt;队列&lt;/h4&gt; &lt;p&gt;队列，队列是一种特殊的线性数据结构，队列只能允许在队头，队尾进行添加和查询等相关操作。队列又有单项有序队列，双向队列，阻塞队列等。&lt;/p&gt; &lt;p&gt;Queue这种数据结构注定了基本操作方法有：add(E e)加入队列，remove(),poll()等方法。&lt;/p&gt; &lt;p&gt;队列在Java语言环境中是使用频率相当高的数据结构，所有其实现的类也很多来满足不同场景。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/Javadlsxl.jpg" alt="Java队列实现类.jpg" title="Java队列实现类.jpg" /&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;使用场景也非常多，如线程池，mq，连接池等。&lt;/p&gt; &lt;/blockquote&gt; &lt;h4&gt;串&lt;/h4&gt; &lt;p&gt;串：也称字符串，是由N个字符组成的优先序列。在Java里面就是指String,而String里面是由chat[]来进行储存。&lt;/p&gt; &lt;p&gt;KMP算法： 这个算法一定要牢记，Java数据结构这本书里面针对字符串的查找匹配算法也只介绍了一种。关键点就是：在字符串比对的时候，主串的比较位置不需要回退的问题。&lt;/p&gt; &lt;h3&gt;二、Java数据结构之：非线性数据结构&lt;/h3&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;非线性数据结构：常见的有：多维数组，集合，树，图，散列表(hash).&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h4&gt;多维数组&lt;/h4&gt; &lt;p&gt;一维数组前面咱也提到了，多维数组无非就是String [][],int[][]等。Java里面很少提供这样的工具类，而java里面tree和图底层的native方法用了多维数组来储存。&lt;/p&gt; &lt;h4&gt;集合&lt;/h4&gt; &lt;p&gt;由一个或多个确定的元素所构成的整体叫做集合。在Java里面可以去广义的去理解为实现了Collection接口的类都叫集合。 &lt;img src="https://img.maruifu.com/images/blog/blogimg/javajh.jpg" alt="集合.jpg" title="集合.jpg" /&gt;&lt;/p&gt; &lt;h4&gt;树&lt;/h4&gt; &lt;p&gt;树形结构，作者觉得它是一种特殊的链形数据结构。最少有一个根节点组成，可以有多个子节点。树，显然是由递归算法组成。 树的特点：&lt;/p&gt; &lt;p&gt;在一个树结构中，有且仅有一个结点没有直接父节点，它就是根节点。 除了根节点，其他结点有且只有一个直接父节点 每个结点可以有任意多个直接子节点。 树的数据结构又分如下几种：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;自由树/普通树：对子节点没有任何约束。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/zys.jpg" alt="自由树.jpg" title="自由树.jpg" /&gt;&lt;/p&gt; &lt;ol start="2"&gt; &lt;li&gt;二叉树：每个节点最多含有两个子节点的树称为二叉树。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;2.1) 一般二叉树：每个子节点的父亲节点不一定有两个子节点的二叉树成为一般二叉树。&lt;/p&gt; &lt;p&gt;2.2) 完全二叉树：对于一颗二叉树，假设其深度为d（d&amp;gt;1）。除了第d层外，其它各层的节点数目均已达最大值，且第d层所有节点从左向右连续地紧密排列，这样的二叉树被称为完全二叉树；&lt;/p&gt; &lt;p&gt;2.3) 满二叉树：所有的节点都是二叉的二叉树成为满二叉树。 &lt;img src="https://img.maruifu.com/images/blog/blogimg/mrcs.jpg" alt="满二叉树" title="满二叉树" /&gt;&lt;/p&gt; &lt;ol start="3"&gt; &lt;li&gt;二叉搜索树/BST：binary search tree,又称二叉排序树、二叉查找树。是有序的。要点：如果不为空，那么其左子树节点的值都小于根节点的值；右子树节点的值都大于根节点的值。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/rcsss.jpg" alt="二叉搜索树。jpg" title="二叉搜索树。jpg" /&gt;&lt;/p&gt; &lt;p&gt;3.1) 二叉平衡树：二叉搜索树，是有序的排序树，但左右两边包括子节点不一定平衡，而二叉平衡树是排序树的一种，并且加点条件，就是任意一个节点的两个叉的深度差不多（比如差值的绝对值小于某个常数，或者一个不能比另一个深出去一倍之类的）。这样的树可以保证二分搜索任意元素都是O(log n)的，一般还附带带有插入或者删除某个元素也是O(log n)的的性质。&lt;/p&gt; &lt;p&gt;为了实现，二叉平衡树又延伸出来了一些算法，业界常见的有AVL、和红黑算法，所以又有以下两种树：&lt;/p&gt; &lt;p&gt;3.1.1) AVL树：最早的平衡二叉树之一。应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树。&lt;/p&gt; &lt;p&gt;3.1.2) 红黑树：通过制定了一些红黑标记和左右旋转规则来保证二叉树平衡。&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;红黑树的5条性质：&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;p&gt;每个结点要么是红的，要么是黑的。 根结点是黑的。 每个叶结点（叶结点即指树尾端NIL指针或NULL结点）是黑的。 如果一个结点是红的，那么它的俩个儿子都是黑的。 对于任一结点而言，其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/hhs.jpg" alt="红黑树.jpg" title="红黑树.jpg" /&gt;&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;B-tree：又称B树、B-树。又叫平衡(balance)多路查找树。树中每个结点最多含有m个孩子（m&amp;gt;=2）。它类似普通的平衡二叉树，不同的一点是B-树允许每个节点有更多的子节点。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/Bshu.jpg" alt="B树.jpg" title="B树.jpg" /&gt;&lt;/p&gt; &lt;ol start="4"&gt; &lt;li&gt;B+tree：又称B+。是B-树的变体，也是一种多路搜索树。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/B+shu.jpg" alt="B+树.jpg" title="B+树.jpg" /&gt;&lt;/p&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;树总结： 树在Java里面应用的也比较多。非排序树，主要用来做数据储存和展示。而排序树，主要用来做算法和运算，HashMap里面的TreeNode就用到了红黑树算法。而B+树在数据库的索引原理里面有典型的应用。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h4&gt;Hash&lt;/h4&gt; &lt;p&gt;Hash概念：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Hash，一般翻译做“散列”，也有直接音译为“哈希”的，就是把任意长度的输入（又叫做预映射， pre-image），变换成固定长度的输出，该输出就是散列值。一般通过Hash算法实现。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;所谓的Hash算法都是散列算法，把任意长度的输入，变换成固定长度的输出，该输出就是散列值.（如：MD5,SHA1,加解密算法等）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Java中的hashCode：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;我们都知道所有的class都是Object的子类，既所有的class都会有默认Object.java里面的hashCode的方法，如果自己没有重写，默认情况就是native方法通过对象的内存的+对象的值然后通过hash散列算法计算出来个int的数字。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;最大的特性是：不同的对象，不同的值有可能计算出来的hashCode可能是一样的。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Hash表：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Java中数据存储方式最底层的两种结构，一种是数组，另一种就是链表。而Hash表就是综合了这两种数据结构。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;如：HashTable,HashMap。这个时候就得提一下HashMap的原理了，默认16个数组储存，通过Hash值取模放到不同的桶里面去。（注意：JDK1.8此处算法又做了改进，数组里面的值会演变成树形结构。）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;哈希表具有较快（常量级）的查询速度，及相对较快的增删速度，所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”，我们可以理解为“链表的数组”。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/haxibiao.jpg" alt="哈希表" title="哈希表" /&gt;&lt;/p&gt; &lt;p&gt;一致性Hash：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;我们查看一下HashMap的原理，其实发现Hash很好的解决了单体应用情况下的数据查找和插入的速度问题。但是毕竟单体应用的储存空间是有限的，所有在分布式环境下，应运而生了一致性Hash算法。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;用意和hashCode的用意一样，只不过它是取模放在不同的IP机器上而已。具体算法可以找一下相关资料。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;而一致性Hash需要注意的就是默认分配的桶比较多些，而当其中一台机器挂了，影响的面比较小一些。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;需要注意的是，相同的内容算出来的hash一定是一样的。既：幂等性。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/yizhixingHash.jpg" alt="一致性Hash.jpg" title="一致性Hash.jpg" /&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;第二部分：Java基本算法&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;理解了Java数据结构，还必须要掌握一些常见的基本算法。 理解算法之前必须要先理解的几个算法的概念：&lt;/p&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;空间复杂度：一句来理解就是，此算法在规模为n的情况下额外消耗的储存空间。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;时间复杂度：一句来理解就是，此算法在规模为n的情况下，一个算法中的语句执行次数称为语句频度或时间频度。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;ul&gt; &lt;li&gt;稳定性：主要是来描述算法，每次执行完，得到的结果都是一样的，但是可以不同的顺序输入，可能消耗的时间复杂度和空间复杂度不一样。&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h3&gt;二分查找算法&lt;/h3&gt; &lt;p&gt;二分查找又称折半查找，优点是比较次数少，查找速度快，平均性能好，占用系统内存较少；其缺点是要求待查表为有序表，且插入删除困难。这个是基础，最简单的查找算法了。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt; public static void main(String[] args) {         int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};         System.out.println(binSearch(srcArray, 28));     }     /**      * 二分查找普通循环实现      *      * @param srcArray 有序数组      * @param key 查找元素      * @return      */     public static int binSearch(int srcArray[], int key) {         int mid = srcArray.length / 2; //        System.out.println(&amp;quot;=:&amp;quot;+mid);         if (key == srcArray[mid]) {             return mid;         }  //二分核心逻辑         int start = 0;         int end = srcArray.length - 1;         while (start &amp;lt;= end) { //            System.out.println(start+&amp;quot;=&amp;quot;+end);             mid = (end - start) / 2 + start;             if (key &amp;lt; srcArray[mid]) {                 end = mid - 1;             } else if (key &amp;gt; srcArray[mid]) {                 start = mid + 1;             } else {                 return mid;             }         }         return -1;     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;二分查找算法如果没有用到递归方法的话，只会影响CPU。对内存模型来说影响不大。时间复杂度log2n，2的开方。空间复杂度是2。一定要牢记这个算法。应用的地方也是非常广泛，平衡树里面大量采用。&lt;/p&gt; &lt;h3&gt;递归算法&lt;/h3&gt; &lt;p&gt;递归简单理解就是方法自身调用自身。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt; public static void main(String[] args) {         int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};         System.out.println(binSearch(srcArray, 0,15,28));     }     /**      * 二分查找递归实现      *      * @param srcArray  有序数组      * @param start 数组低地址下标      * @param end   数组高地址下标      * @param key  查找元素      * @return 查找元素不存在返回-1      */     public static int binSearch(int srcArray[], int start, int end, int key) {         int mid = (end - start) / 2 + start;         if (srcArray[mid] == key) {             return mid;         }         if (start &amp;gt;= end) {             return -1;         } else if (key &amp;gt; srcArray[mid]) {             return binSearch(srcArray, mid + 1, end, key);         } else if (key &amp;lt; srcArray[mid]) {             return binSearch(srcArray, start, mid - 1, key);         }         return -1;     }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;递归几乎会经常用到，需要注意的一点是：递归不光影响的CPU。JVM里面的线程栈空间也会变大。所以当递归的调用链长的时候需要-Xss设置线程栈的大小。&lt;/p&gt; &lt;h3&gt;八大排序算法&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;一、直接插入排序（Insertion Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;二、希尔排序（Shell Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;三、选择排序（Selection Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;四、堆排序（Heap Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;五、冒泡排序（Bubble Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;六、快速排序（Quick Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;七、归并排序（Merging Sort）&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;八、基数排序（Radix Sort）&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;八大算法，网上的资料就比较多了。&lt;/p&gt; &lt;p&gt;吐血推荐参考资料：git hub &lt;a href="https://itimetraveler.github.io/2017/07/18/%E5%85%AB%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93%E4%B8%8Ejava%E5%AE%9E%E7%8E%B0/" title="八大算法详解" target="_blank"&gt;八大排序算法详解&lt;/a&gt;。此大神比作者讲解的还详细，作者就不在这里，描述重复的东西了，作者带领大家把重点的两个强调一下,此两个是必须要掌握的。&lt;/p&gt; &lt;h4&gt;冒泡排序&lt;/h4&gt; &lt;p&gt;基本思想：&lt;/p&gt; &lt;p&gt;冒泡排序（Bubble Sort）是一种简单的排序算法。它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换，也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/maopaopaixu.gif" alt="冒泡排序.jpg" title="冒泡排序.jpg" /&gt;&lt;/p&gt; &lt;p&gt;以下是冒泡排序算法复杂度:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;平均时间复杂度&lt;/th&gt;&lt;th align="center"&gt;最好情况&lt;/th&gt;&lt;th align="center"&gt;最坏情况&lt;/th&gt;&lt;th align="right"&gt;空间复杂度&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;td align="center"&gt;O(n)&lt;/td&gt;&lt;td align="center"&gt;O(n²)&lt;/td&gt;&lt;td align="right"&gt;O(1)&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;p&gt;Tips: 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法.&lt;/p&gt; &lt;pre&gt;&lt;code class="language-Java"&gt;/**  * 冒泡排序  *  * ①. 比较相邻的元素。如果第一个比第二个大，就交换他们两个。  * ②. 对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。这步做完后，最后的元素会是最大的数。  * ③. 针对所有的元素重复以上的步骤，除了最后一个。  * ④. 持续每次对越来越少的元素重复上面的步骤①~③，直到没有任何一对数字需要比较。  * @param arr  待排序数组  */ public static void bubbleSort(int[] arr){     for (int i = arr.length; i &amp;gt; 0; i--) {      //外层循环移动游标         for(int j = 0; j &amp;lt; i &amp;amp;&amp;amp; (j+1) &amp;lt; i; j++){    //内层循环遍历游标及之后(或之前)的元素             if(arr[j] &amp;gt; arr[j+1]){                 int temp = arr[j];                 arr[j] = arr[j+1];                 arr[j+1] = temp;                 System.out.println(&amp;quot;Sorting: &amp;quot; + Arrays.toString(arr));             }         }     } }  &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;快速排序&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/kuaisupaixu.gif" alt="快速排序.jpg" title="快速排序.jpg" /&gt;&lt;/p&gt; &lt;p&gt;快速排序使用分治策略来把一个序列（list）分为两个子序列（sub-lists）。步骤为：&lt;/p&gt; &lt;p&gt;①. 从数列中挑出一个元素，称为”基准”（pivot）。&lt;/p&gt; &lt;p&gt;②. 重新排序数列，所有比基准值小的元素摆放在基准前面，所有比基准值大的元素摆在基准后面（相同的数可以到任一边）。在这个分区结束之后，该基准就处于数列的中间位置。这个称为分区（partition）操作。&lt;/p&gt; &lt;p&gt;③. 递归地（recursively）把小于基准值元素的子数列和大于基准值元素的子数列排序。&lt;/p&gt; &lt;p&gt;递归到最底部时，数列的大小是零或一，也就是已经排序好了。这个算法一定会结束，因为在每次的迭代（iteration）中，它至少会把一个元素摆到它最后的位置去。&lt;/p&gt; &lt;p&gt;代码实现：&lt;/p&gt; &lt;p&gt;用伪代码描述如下：&lt;/p&gt; &lt;p&gt;①. i = L; j = R; 将基准数挖出形成第一个坑a[i]。&lt;/p&gt; &lt;p&gt;②．j--，由后向前找比它小的数，找到后挖出此数填前一个坑a[i]中。&lt;/p&gt; &lt;p&gt;③．i++，由前向后找比它大的数，找到后也挖出此数填到前一个坑a[j]中。&lt;/p&gt; &lt;p&gt;④．再重复执行②，③二步，直到i==j，将基准数填入a[i]中。&lt;/p&gt; &lt;p&gt;快速排序采用“分而治之、各个击破”的观念，此为原地（In-place）分区版本。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/kuaisupaixu2.gif" alt="快速排序2.jpg" title="快速排序2.jpg" /&gt;&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;/**  * 快速排序（递归）  *  * ①. 从数列中挑出一个元素，称为&amp;quot;基准&amp;quot;（pivot）。  * ②. 重新排序数列，所有比基准值小的元素摆放在基准前面，所有比基准值大的元素摆在基准后面（相同的数可以到任一边）。在这个分区结束之后，该基准就处于数列的中间位置。这个称为分区（partition）操作。  * ③. 递归地（recursively）把小于基准值元素的子数列和大于基准值元素的子数列排序。  * @param arr   待排序数组  * @param low   左边界  * @param high  右边界  */ public static void quickSort(int[] arr, int low, int high){     if(arr.length &amp;lt;= 0) return;     if(low &amp;gt;= high) return;     int left = low;     int right = high;     int temp = arr[left];   //挖坑1：保存基准的值     while (left &amp;lt; right){         while(left &amp;lt; right &amp;amp;&amp;amp; arr[right] &amp;gt;= temp){  //坑2：从后向前找到比基准小的元素，插入到基准位置坑1中             right--;         }         arr[left] = arr[right];         while(left &amp;lt; right &amp;amp;&amp;amp; arr[left] &amp;lt;= temp){   //坑3：从前往后找到比基准大的元素，放到刚才挖的坑2中             left++;         }         arr[right] = arr[left];     }     arr[left] = temp;   //基准值填补到坑3中，准备分治递归快排     System.out.println(&amp;quot;Sorting: &amp;quot; + Arrays.toString(arr));     quickSort(arr, low, left-1);     quickSort(arr, left+1, high); }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以下是快速排序算法复杂度:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;平均时间复杂度&lt;/th&gt;&lt;th align="center"&gt;最好情况&lt;/th&gt;&lt;th align="center"&gt;最坏情况&lt;/th&gt;&lt;th align="right"&gt;空间复杂度&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;O(nlog₂n)&lt;/td&gt;&lt;td align="center"&gt;O(nlog₂n)&lt;/td&gt;&lt;td align="center"&gt;O(n²)&lt;/td&gt;&lt;td align="right"&gt;O(1)（原地分区递归版）&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;快速排序排序效率非常高。 虽然它运行最糟糕时将达到O(n²)的时间复杂度, 但通常平均来看, 它的时间复杂为O(nlogn), 比同样为O(nlogn)时间复杂度的归并排序还要快. 快速排序似乎更偏爱乱序的数列, 越是乱序的数列, 它相比其他排序而言, 相对效率更高.&lt;/p&gt; &lt;p&gt;最后，作者希望让大家对《Java数据结构》整体有个全面的了解,知道什么是数据结构，离我们工作中有多远，而不是一个深不可测的神秘物件。里面的细节，篇幅有限可能不能描述完，但是只要同学们的方向没有搞错，那只要针对每个点再详细的看看即可。&lt;/p&gt; &lt;p&gt;面试和工作，这些都是离不开的，当同学们有个完整的认识之后，一定要在工作中留心，留意每个用到的地方。&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 18 Jan 2018 01:31:00 GMT</pubDate>
    </item>
    <item>
      <title>Java虚拟机运行时数据区域</title>
      <link>https://maruifu.cn/article/72</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;身为Java程序员你不知道Java虚拟机，你好意思么！知道Java虚拟机连它运行时的数据区域都不知道，你敢说你知道Java虚拟机！！&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h1&gt;运行时数据区域&lt;/h1&gt; &lt;p&gt;Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途，以及创建和销毁的时间，有的区域随着虚拟机进程的启动而存在，有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范（JavaSE 7版）》的规定，Java虚拟机所管理的内存将会包括以下几个运行时数据区域，如图所示&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/xunijiyunxingshujuqu.png" alt="虚拟机运行时数据区.png" title="虚拟机运行时数据区.png" /&gt;&lt;/p&gt; &lt;h2&gt;程序计数器&lt;/h2&gt; &lt;p&gt;程序计数器（Program  Counter  Register）是一块较小的内存空间，它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里（仅是概念模型，各种虚拟机可能会通过一些更高效的方式去实现），字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。&lt;/p&gt; &lt;p&gt;由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的，在任何一个确定的时刻，一个处理器（对于多核处理器来说是一个内核）都只会执行一条线程中的指令。因此，为了线程切换后能恢复到正确的执行位置，每条线程都需要有一个独立的程序计数器，各条线程之间计数器互不影响，独立存储，我们称这类内存区域为“线程私有”的内存。因为CPU通过时间轮转来为线程服务，为了线程切换后能够恢复的正确的位置，在每一个线程都保存一个程序计数器&lt;/p&gt; &lt;p&gt;如果线程正在执行的是一个Java方法，这个计数器记录的是正在执行的虚拟机字节码指令的地址；如果正在执行的是Native方法，这个计数器值则为空（Undefined）。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。&lt;/p&gt; &lt;h2&gt;Java虚拟机栈&lt;/h2&gt; &lt;p&gt;与程序计数器一样，Java虚拟机栈（Java Virtual Machine Stacks）也是线程私有的，它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型：每个方法在执行的同时都会创建一个栈帧（Stack  Frame[1]）用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程，就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。&lt;/p&gt; &lt;p&gt;经常有人把Java内存区分为堆内存（Heap）和栈内存（Stack），这种分法比较粗糙，Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”笔者在后面会专门讲述，而所指的“栈”就是现在讲的虚拟机栈，或者说是虚拟机栈中局部变量表部分。&lt;/p&gt; &lt;p&gt;局部变量表存放了编译期可知的各种基本数据类型（boolean、byte、char、short、int、float、long、double）、对象引用（reference类型，它不等同于对象本身，可能是一个指向对象起始地址的引用指针，也可能是指向一个代表对象的句柄或其他与此对象相关的位置）和returnAddress类型（指向了一条字节码指令的地址）。&lt;/p&gt; &lt;p&gt;其中64位长度的long和double类型的数据会占用2个局部变量空间（Slot），其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配，当进入一个方法时，这个方法需要在帧中分配多大的局部变量空间是完全确定的，在方法运行期间不会改变局部变量表的大小。&lt;/p&gt; &lt;p&gt;在Java虚拟机规范中，对这个区域规定了两种异常状况：如果线程请求的栈深度大于虚拟机所允许的深度，将抛出StackOverflowError异常；如果虚拟机栈可以动态扩展（当前大部分的Java虚拟机都可动态扩展，只不过Java虚拟机规范中也允许固定长度的虚拟机栈），如果扩展时无法申请到足够的内存，就会抛出OutOfMemoryError异常。使用-Xss来设置栈大小。栈帧是方法运行时的基础数据结构，&lt;/p&gt; &lt;h2&gt;本地方法栈&lt;/h2&gt; &lt;p&gt;本地方法栈（Native  Method  Stack）与虚拟机栈所发挥的作用是非常相似的，它们之间的区别不过是虚拟机栈为虚拟机执行Java方法（也就是字节码）服务，而本地方法栈则为虚拟机使用到的Native方法服务。&lt;/p&gt; &lt;p&gt;在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定，因此具体的虚拟机可以自由实现它。&lt;/p&gt; &lt;p&gt;甚至有的虚拟机（譬如Sun  HotSpot虚拟机）直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样，本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。&lt;/p&gt; &lt;h2&gt;Java堆&lt;/h2&gt; &lt;p&gt;对于大多数应用来说，Java堆（Java  Heap）是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域，在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例，几乎所有的对象实例都在这里分配内存。&lt;/p&gt; &lt;p&gt;这一点在Java虚拟机规范中的描述是：所有的对象实例以及数组都要在堆上分配[1]，但是随着JIT编译器的发展与逃逸分析技术逐渐成熟，栈上分配、标量替换[2]优化技术将会导致一些微妙的变化发生，所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。&lt;/p&gt; &lt;p&gt;Java堆是垃圾收集器管理的主要区域，因此很多时候也被称做“GC堆”（GarbageCollected Heap，幸好国内没翻译成“垃圾堆”）。从内存回收的角度来看，由于现在收集器基本都采用分代收集算法，所以Java堆中还可以细分为：新生代和老年代；再细致一点的有Eden空间、From  Survivor空间、To  Survivor空间等。从内存分配的角度来看，线程共享的Java堆中可能划分出多个线程私有的分配缓冲区（Thread Local Allocation Buffer,TLAB）。不过无论如何划分，都与存放内容无关，无论哪个区域，存储的都仍然是对象实例，进一步划分的目的是为了更好地回收内存，或者更快地分配内存。 根据Java虚拟机规范的规定，Java堆可以处于物理上不连续的内存空间中，只要逻辑上是连续的即可，就像我们的磁盘空间一样。在实现时，既可以实现成固定大小的，也可以是可扩展的，不过当前主流的虚拟机都是按照可扩展来实现的（通过-Xmx和-Xms控制）。如果在堆中没有内存完成实例分配，并且堆也无法再扩展时，将会抛出OutOfMemoryError异常。&lt;/p&gt; &lt;p&gt;[1]&lt;code&gt;Java虚拟机规范中的原文：The heap is the runtime data area from which memory for all class instances and arrays is allocated&lt;/code&gt;&lt;/p&gt; &lt;h2&gt;方法区&lt;/h2&gt; &lt;p&gt;方法区（Method Area）与Java堆一样，是各个线程共享的内存区域，它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫做Non-Heap（非堆），目的应该是与Java堆区分开来。&lt;/p&gt; &lt;p&gt;对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说，很多人都更愿意把方法区称为“永久代”（Permanent  Generation），本质上两者并不等价，仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区，或者说使用永久代来实现方法区而已，这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存，能够省去专门为方法区编写内存管理代码的工作。对于其他虚拟机（如BEA JRockit、IBM J9等）来说是不存在永久代的概念的。原则上，如何实现方法区属于虚拟机实现细节，不受虚拟机规范约束，但使用永久代来实现方法区，现在看来并不是一个好主意，因为这样更容易遇到内存溢出问题（永久代有-XX：MaxPermSize的上限，J9和JRockit只要没有触碰到进程可用内存的上限，例如32位系统中的4GB，就不会出现问题），而且有极少数方法（例如String.intern（））会因这个原因导致不同虚拟机下有不同的表现。因此，对于HotSpot虚拟机，根据官方发布的路线图信息，现在也有放弃永久代并逐步改为采用Native  Memory来实现方法区的规划了[1]，在目前已经发布的JDK 1.7的HotSpot中，已经把原本放在永久代的字符串常量池移出。&lt;/p&gt; &lt;p&gt;Java虚拟机规范对方法区的限制非常宽松，除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外，还可以选择不实现垃圾收集。相对而言，垃圾收集行为在这个区域是比较少出现的，但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载，一般来说，这个区域的回收“成绩”比较难以令人满意，尤其是类型的卸载，条件相当苛刻，但是这部分区域的回收确实是必要的。在Sun公司的BUG列表中，曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。使用-XX:PermSize和-XX:MaxPermSize设置方法区的大小。&lt;/p&gt; &lt;p&gt;[1]&lt;code&gt;JEP 122-Remove the Permanent Generation：http://openjdk.java.net/jeps/122。&lt;/code&gt;&lt;/p&gt; &lt;h2&gt;运行时常量池&lt;/h2&gt; &lt;p&gt;运行时常量池（Runtime  Constant  Pool）是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外，还有一项信息是常量池（Constant Pool Table），用于存放编译期生成的各种字面量和符号引用，这部分内容将在类加载后进入方法区的运行时常量池中存放。&lt;/p&gt; &lt;p&gt;Java虚拟机对Class文件每一部分（自然也包括常量池）的格式都有严格规定，每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行，但对于运行时常量池，Java虚拟机规范没有做任何细节的要求，不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。&lt;/p&gt; &lt;p&gt;不过，一般来说，除了保存Class文件中描述的符号引用外，还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性，Java语言并不要求常量一定只有编译期才能产生，也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池，运行期间也可能将新的常量放入池中，这种特性被开发人员利用得比较多的便是String类的intern（）方法。既然运行时常量池是方法区的一部分，自然受到方法区内存的限制，当常量池无法再申请到内存时会抛出OutOfMemoryError异常。&lt;/p&gt; &lt;h2&gt;直接内存&lt;/h2&gt; &lt;p&gt;直接内存（Direct  Memory）并不是虚拟机运行时数据区的一部分，也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用，而且也可能导致OutOfMemoryError异常出现。&lt;/p&gt; &lt;p&gt;在JDK 1.4中新加入了NIO（New Input/Output）类，引入了一种基于通道（Channel）与缓冲区（Buffer）的I/O方式，它可以使用Native函数库直接分配堆外内存，然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能，因为避免了在Java堆和Native堆中来回复制数据。&lt;/p&gt; &lt;p&gt;显然，本机直接内存的分配不会受到Java堆大小的限制，但是，既然是内存，肯定还是会受到本机总内存（包括RAM以及SWAP区或者分页文件）大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时，会根据实际内存设置-Xmx等参数信息，但经常忽略直接内存，使得各个内存区域总和大于物理内存限制（包括物理的和操作系统级的限制），从而导致动态扩展时出现OutOfMemoryError异常。&lt;/p&gt; &lt;h1&gt;垃圾收集器与内存分配策略&lt;/h1&gt; &lt;h2&gt;如何判断对象已死&lt;/h2&gt; &lt;h3&gt;引用计数算法&lt;/h3&gt; &lt;p&gt;在对象中保存一个引用计数器，如果该对象在一个地方被引用，则引用计数器值加1，如果有一个地方的引用失效则计数器减1。在任意时刻计数器的值为0则表示对象已死。优点是简单，速度快。缺点是：循环引用问题。&lt;/p&gt; &lt;h3&gt;根搜索算法&lt;/h3&gt; &lt;p&gt;当一个对象到GC Roots没有任何引用链，则表示该对象已死&lt;/p&gt; &lt;h4&gt;哪些对象可以当做GC Roots&lt;/h4&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Java虚拟机栈中的引用的对象。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;本地方法栈中的引用的对象。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;方法区中常量引用的对象。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;方法区中静态变量引用的对象。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;Java中的四种引用&lt;/h4&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;强引用：例如Object obj = new Object();只要强引用还在，则对象一定不会被回收&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;软引用：当将要发生内存溢出时，GC则将这些对象列入垃圾回收的范围。如果回收后仍然内存空间不足，则抛出OutOfMemoryError异常。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;弱引用：弱引用关联的对象只能活到下一次垃圾回收之前。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;虚引用：虚引用完全不影响对象的生存周期，只是在垃圾回收时收到一个系统通知。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h4&gt;对象的二次标记&lt;/h4&gt; &lt;p&gt;当对象到GC Roots不可达时，并不一定被回收。还回经历两次标记的过程。&lt;/p&gt; &lt;p&gt;当对象到GC Roots不可达时，它会被第一次标记，并被筛选。筛选的条件是是否有必要执行finalize(), 如果对象没有覆finalize()或者已经被JVM执行了finalize()，则认为没有必要执行（直接被回收）。如果认为有必要执行，则将对象存放到一个F-Queue队列中，JVM会自动创建一个低级线程Finalizer用来执行finalize()。这里执行只是触发该方法，并不会等待该方法执行完成。执行finalize()方法是对象逃脱别回收的最后一次机会。GC 会对F-Queue中的对象进行二次标记，如果在期间被GC Roots引用链上的对象重新连接，则不会被回收。&lt;/p&gt; &lt;h2&gt;方法区的回收&lt;/h2&gt; &lt;p&gt;方法区主要回收常量池和无用的类。&lt;/p&gt; &lt;p&gt;如何判断一个类是无用的类&lt;/p&gt; &lt;p&gt;要满足下面三个条件：&lt;/p&gt; &lt;p&gt;(1) 该类的所有对象都已经被回收，Java堆中没有该类的任何实例。&lt;/p&gt; &lt;p&gt;(2) 该类的类加载器已经被回收。&lt;/p&gt; &lt;p&gt;(3) 该类的java.lang.Class对象没有在任何地方被使用。没有在任何地方通过反射访问该类的方法。&lt;/p&gt; &lt;h2&gt;垃圾回收算法&lt;/h2&gt; &lt;p&gt;(1) 标记清除算法：第一步将所有要回收的对象进行标记，第二步回收掉所有被标记的对象。优点：简单；缺点：标记和清除效率都较低，并且会使得内存中出现很对碎片。&lt;/p&gt; &lt;p&gt;(2) 复制算法：将内存区域分成一个较大的Eden区域，两个较小的Survivor区域。分配空间时，每次使用Eden区域和其中一块Survivor区域。在垃圾回收时，将Eden区域和Survivor区域存活的对象复制到另一块Survivor中。&lt;/p&gt; &lt;p&gt;(3) 标记-整理算法：第一步对所有要回收对对象进行标记，第二步将存活的对象移到一端，将边界以外的所有对象回收。&lt;/p&gt; &lt;p&gt;(4) 分代收集算法。按照对象的生存周期将内存分成几个区域。在每个区域使用不同的算法。一般把Java堆分成新生代和老年代，对新生代使用复制算法，对老年代使用标记清除或者标记整理算法。&lt;/p&gt; &lt;h2&gt;垃圾收集器&lt;/h2&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/12/19/1524034851053136d4b3f96.png" alt="百度资深架构师深度钻研JVM的个人理解以及整理" title="百度资深架构师深度钻研JVM的个人理解以及整理" /&gt;&lt;/p&gt; &lt;p&gt;(1) Serial收集器：是单线程的，使用的是复制算法。使用一条线程去垃圾回收时，必须要停止其他工作线程。&lt;/p&gt; &lt;p&gt;(2) ParNew收集器：是Serial的多线程版本。&lt;/p&gt; &lt;p&gt;(3) Parallel Scavenge：目标主要是用来控制CPU的吞吐量。使用的是复制算法&lt;/p&gt; &lt;p&gt;(4) Serial Old收集器：Serial的老年版本。使用的是标记整理算法。&lt;/p&gt; &lt;p&gt;(5) Parallel Old收集器:Parallel Scavenge的老年版本。使用的是标记整理算法。&lt;/p&gt; &lt;p&gt;(6) CMS收集器：以获取最短停顿时间为目标的。使用的是标记清除算法。在标记和清除阶段使用的是并发操作。&lt;/p&gt; &lt;p&gt;(7) G1收集器:将Java堆分成若干个大小固定的区域，使用的是标记清除算法。跟踪没一个区域的垃圾堆积程度，并维持一个优先级队列，根据允许的收集时间，选择垃圾堆积最多的区域进行回收。&lt;/p&gt; &lt;h2&gt;内存分配与回收策略&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;对象优先在Eden区域分配&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对象优先在Eden区域分配，如果Eden区域没有足够的空间分配，则虚拟机发起一次Minor GC。-XX:SurvivorRatio用来设置Eden区域和Survivor区域的大小比值。&lt;/p&gt; &lt;p&gt;Eden区域空间不足，发起一次Minor GC，将Eden区域和Survivor中存活的对象复制到另一个Survivor中，如果Survivor无法容纳所有存活的对象，则根据分配担保机制，将其转移到老年代。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;大对象直接进入老年代&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;大对象指需要大量连续内存空间的对象，例如大数组。使用 -XX:PretenureSizeThreshold来设置阈值，如果对象答案与这个阈值则直接进入老年代。这样做的目的避免对象在Eden区域以及两个Survivor区域发生大量拷贝。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;长期存活的对象进入老年代&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Java虚拟机给每个对象定义一个Age计数器，如果对象在Eden区域出生，经过一次GC仍然存活，将其复制到Survivor区域，如果能被容纳则将Age加1。当Age的值大于等于MaxTenuringThreshold时进入老年代。阈值设置使用-XX:MaxTenuringThreshold。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;动态对象年龄判定&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;对应并不总是等到年龄大于maxTenuringThreshold才进入老年代。如果Survivor中相同年龄的对象的总大小大于等于Survivor空间的一半，则将所有大于等于该年龄的对象移入到老年代。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;空间分配担保&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;在发生minor GC之前，Java虚拟机会检测之前每次进入老年代的平均大小是否大于老年代的剩余大小。如果大于老年代的大小，则将进行一次Full GC。如果不大于，则查看HandlePromotionFailure是否设置为true, 若果是则进行一次minor GC. 如果为False则进行一次Full GC。&lt;/p&gt; &lt;h1&gt;个人心得总结&lt;/h1&gt; &lt;p&gt;常量池用于存放编译期期间生成的各种字面量和符号，在类加载后进入方法区的运行时常量池。&lt;/p&gt; &lt;p&gt;Java语言并不要求常量一定在编译期才能产生。并非一定是在class编译期中预置的才能进去方法区中的运行时常量池，在运行期间也可以将常量放入。运用的最多的就是String类的intern（）方法。运行时常量池是方法区的一部分，当无法申请的内存是会抛出OOS异常。&lt;/p&gt; &lt;p&gt;对象的创建，当遇到一个new指令时，会先去检查这个指令的参数在常量池中是否能定位到一个类的符号引用，在检查这个符号引用代表的类是否被加载，解析和初始化够。在类加载通过后，对象所需要的大小可以完全确定。在Java堆中，拥有一个指针座位分界点的指示器，当需要分配内存时，会指向未被分配内存的区域，将其挪动一段与对象大小相等的距离，称为指针碰撞。如果内存不是规整的，会有一个列表，上面记录哪些区域是可用的，当需要是划分列表中一块足够大的空间分配给对象，然后更新列表上的记录，称为空闲列表。Java堆是否规整由GC收集器是否带有压缩整理功能决定。Serial等带有Compact过程的采用指针碰撞，CMS这种基于Mark-Sweep算法的收集器，采用空闲列表。&lt;/p&gt; &lt;p&gt;对象在内存中存储的布局可以分为3部分：对象头，实例数据，对齐填充。&lt;/p&gt; &lt;p&gt;对象头包含两部分，第一部分是自己运行时的数据，包裹哈希吗，GC分代年龄，锁状态的标志，线程自带的锁，偏向线程ID，偏向时间戳等。另一部分是类型指针，指向它的类元数据。虚拟机可以通过这个指针来确定它是哪个类的实例。如果是Java数组的话，在对象头中还必须有一块用于用于记录数组长度的数据，虚拟机可以通过普通java对象的元数据确定Java对象的大小，但是从数组的元数据中无法确定数组的大小。&lt;/p&gt; &lt;p&gt;对齐填充并不是必然存在的，起到一个占位符的作用，HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的倍数，当对象的实例数据没有对齐的时候，就需要对齐填充来补全。&lt;/p&gt; &lt;p&gt;实例数据是对象真正存储的有效信息，也是在程序中所定义的各种类型的字段内容。存储顺序受到虚拟机分配策略参数和字段在java源码中定义顺序的影响。相同宽度的字段总是被分配到一起。满足这个条件下，父类中定义的方法会出现在子类之前。如果CompactFields为true，子类中较窄的变量也可能会插入到父类变量的空隙之中。&lt;/p&gt; &lt;p&gt;在jdk1.6之前,StringBuilder会在java堆中创建一个实例，调用String.intern（）会把这个实例复制在方法区，所以他们不是一个相同的引用，在jdk1.7后，不会再实现复制，而是在方法区中记录首次出现的实例引用。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 09 Jan 2018 00:46:00 GMT</pubDate>
    </item>
    <item>
      <title>Oracle必知的100道问题</title>
      <link>https://maruifu.cn/article/71</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Oracle必知的100到问题  你不学一下?&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;1.Oracle安装完成后的初始口令？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;　internal/oracle  　　sys/change_on_install  　　system/manager  　　scott/tiger  　　sysman/oem_temp &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2.ORACLE9IAS WEB CACHE的初始默认用户和密码？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;administrator/administrator &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3.oracle 8.0.5怎么创建数据库？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;用orainst。如果有motif界面，可以用orainst /m。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4.oracle 8.1.7怎么创建数据库？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;dbassist &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;5.oracle 9i 怎么创建数据库？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;dbca &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;6.oracle中的裸设备指的是什么？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;裸设备就是绕过文件系统直接访问的储存空间。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;7.oracle如何区分 64-bit/32bit 版本？？？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ sqlplus '/ AS SYSDBA'  SQL*Plus: Release 9.0.1.0.0 - Production on Mon Jul 14 17:01:09 2003  (c) Copyright 2001 Oracle Corporation. All rights reserved.  Connected to:  Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production  With the Partitioning option  JServer Release 9.0.1.0.0 - Production  SQL&amp;gt; select * from v$version;  BANNER  Oracle9i Enterprise Edition Release 9.0.1.0.0 - Production  PL/SQL Release 9.0.1.0.0 - Production  CORE 9.0.1.0.0 Production  TNS for Solaris: Version 9.0.1.0.0 - Production  NLSRTL Version 9.0.1.0.0 - Production  SQL&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;8.SVRMGR什么意思？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;svrmgrl，Server Manager. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;9i下没有，已经改为用SQLPLUS了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sqlplus /nolog &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;变为归档日志型的。&lt;/p&gt; &lt;p&gt;9.请问如何分辨某个用户是从哪台机器登陆ORACLE的？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT machine , terminal FROM V$SESSION; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;10.用什么语句查询字段呢？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;desc table_name 可以查询表的结构  select field_name,... from ... 可以查询字段的值  select * from all_tables where table_name like '%'  select * from all_tab_columns where table_name='？？' &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;11.怎样得到触发器、过程、函数的创建脚本？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;desc user_source  user_triggers &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;12.怎样计算一个表占用的空间的大小？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select owner,table_name,  NUM_ROWS,  BLOCKS*AAA/1024/1024 &amp;quot;Size M&amp;quot;,  EMPTY_BLOCKS,  LAST_ANALYZED  from dba_tables  where table_name='XXX';  Here: AAA is the value of db_block_size ;  XXX is the table name you want to check &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;13.如何查看最大会话数？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM V$PARAMETER WHERE NAME LIKE 'proc%';  SQL&amp;gt;  SQL&amp;gt; show parameter processes  NAME TYPE VALUE  aq_tm_processes integer 1  db_writer_processes integer 1  job_queue_processes integer 4  log_archive_max_processes integer 1  processes integer 200  这里为200个用户。  select * from v$license;  其中sessions_highwater纪录曾经到达的最大会话数。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;14.如何查看系统被锁的事务时间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select * from v$locked_object ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;15.如何以archivelog的方式运行oracle？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;init.ora  log_archive_start = true  RESTART DATABASE &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;16.怎么获取有哪些用户在使用数据库？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select username from v$session; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;17.数据表中的字段最大数是多少？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;表或视图中的最大列数为 1000。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;18.怎样查得数据库的SID ？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select name from v$database;  也可以直接查看 init.ora文件。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;19.如何在Oracle服务器上通过SQLPLUS查看本机IP地址 ？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select sys_context('userenv','ip_address') from dual;  如果是登陆本机数据库，只能返回127.0.0.1，呵呵。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;20.unix 下怎么调整数据库的时间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;su -root  date -u 08010000 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;21.在ORACLE TABLE中如何抓取MEMO类型栏位为空的资料记录？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select remark from oms_flowrec where trim(' ' from remark) is not null ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;22.如何用BBB表的资料去更新AAA表的资料(有关联的字段)？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;UPDATE AAA SET BNS_SNM=(SELECT BNS_SNM FROM BBB WHERE AAA.DPT_NO=BBB.DPT_NO) W  HERE BBB.DPT_NO IS NOT NULL; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;23.P4电脑安装方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;将SYMCJIT.DLL改为SYSMCJIT.OLD &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;24.何查询SERVER是不是OPS？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM V$OPTION;  如果PARALLEL SERVER=TRUE则有OPS能。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;25.何查询每个用户的权限？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM DBA_SYS_PRIVS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;26.如何将表移动表空间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ALTER TABLE TABLE_NAME MOVE TABLESPACE_NAME; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;27.如何将索引移动表空间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ALTER INDEX INDEX_NAME REBUILD TABLESPACE TABLESPACE_NAME; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;28.在LINUX,UNIX下如何启动DBA STUDIO？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;OEMAPP DBASTUDIO &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;29.查询锁的状况的对象有？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;V$LOCK, V$LOCKED_OBJECT, V$SESSION, V$SQLAREA, V$PROCESS ;  查询锁的表的方法:  SELECT S.SID SESSION_ID, S.USERNAME, DECODE(LMODE, 0, 'None', 1, 'Null', 2, 'R  ow-S (SS)', 3, 'Row-X (SX)', 4, 'Share', 5, 'S/Row-X (SSX)', 6, 'Exclusive', T  O_CHAR(LMODE)) MODE_HELD, DECODE(REQUEST, 0, 'None', 1, 'Null', 2, 'Row-S (SS)  ', 3, 'Row-X (SX)', 4, 'Share', 5, 'S/Row-X (SSX)', 6, 'Exclusive', TO_CHAR(RE  QUEST)) MODE_REQUESTED, O.OWNER||'.'||O.OBJECT_NAME||' ('||O.OBJECT_TYPE||')',  S.TYPE LOCK_TYPE, L.ID1 LOCK_ID1, L.ID2 LOCK_ID2 FROM V$LOCK L, SYS.DBA_OBJEC  TS O, V$SESSION S WHERE L.SID = S.SID AND L.ID1 = O.OBJECT_ID ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;30.如何解锁？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ALTER SYSTEM KILL SESSION ‘SID,SERIR#’; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;31.SQLPLUS下如何修改编辑器？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;DEFINE _EDITOR=&amp;quot;&amp;lt;编辑器的完整路经&amp;gt;&amp;quot; -- 必须加上双引号  来定义新的编辑器，也可以把这个写在$ORACLE_HOME/sqlplus/admin/glogin.sql里面使它  永久有效。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;32.ORACLE产生随机函数是？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;DBMS_RANDOM.RANDOM &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;33.LINUX下查询磁盘/CPU竞争状况命令？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Sar -d  sar -r &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;34.查询当前用户对像？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM USER_OBJECTS;  SELECT * FROM DBA_SEGMENTS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;35.如何获取错误信息？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM USER_ERRORS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;36.如何获取链接状况？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM DBA_DB_LINKS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;37.查看数据库字符状况？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM NLS_DATABASE_PARAMETERS;  SELECT * FROM V$NLS_PARAMETERS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;38.查询表空间信息？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM DBA_DATA_FILES; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;39.ORACLE的INTERAL用户要口令？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;修改 SQLNET.ORA   SQLNET.AUTHENTICATION_SERVICES=(NTS) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;40.出现JAVA.EXE的解决办法？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;一般是将ORACLEORAHOMEXIHTTPSERVER改成手工启动可以的，X是8或9。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;41.如何给表、列加注释？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;comment on table 表 is '表注释';  注释已创建。  SQL&amp;gt;comment on column 表.列 is '列注释';  注释已创建。  SQL&amp;gt; select * from user_tab_comments where comments is not null; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;42.如何查看各个表空间占用磁盘情况？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; col tablespace format a20  SQL&amp;gt; select  b.file_id 文件ID号,  b.tablespace_name 表空间名,  b.bytes 字节数,  (b.bytes-sum(nvl(a.bytes,0))) 已使用,  sum(nvl(a.bytes,0)) 剩余空间,  sum(nvl(a.bytes,0))/(b.bytes)*100 剩余百分比  from dba_free_space a,dba_data_files b  where a.file_id=b.file_id  group by b.tablespace_name,b.file_id,b.bytes  order by b.file_id &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;43.如把ORACLE设置为MTS或专用模式？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;dispatchers=&amp;quot;(PROTOCOL=TCP) (SERVICE=SIDXDB)&amp;quot;  加上就是MTS，注释就是专用模式，SID是指你的实例名。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;44.如何才能得知系统当前的SCN号 ？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select max(ktuxescnw * power(2, 32) + ktuxescnb) from x$ktuxe; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;45.请问如何在ORACLE中取毫秒？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;9i之前不支持,9i开始有timestamp.   9i可以用select systimestamp from dual; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;46.如何在字符串里加回车？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select 'Welcome to visit'||chr(10)||'www.CSDN.NET' from dual ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;47.中文是如何排序的？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Oracle9i之前，中文是按照二进制编码进行排序的。  在oracle9i中新增了按照拼音、部首、笔画排序功能。设置NLS_SORT值  SCHINESE_RADICAL_M 按照部首（第一顺序）、笔划（第二顺序）排序  SCHINESE_STROKE_M 按照笔划（第一顺序）、部首（第二顺序）排序  SCHINESE_PINYIN_M 按照拼音排序 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;48.Oracle8i中对像名可以用中文吗？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;可以。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;49.如何改变WIN中SQL*Plus启动选项？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL*PLUS自身的选项设置我们可以在$ORACLE_HOME/sqlplus/admin/glogin.sql中设置。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;50.怎样修改oracel数据库的默认日期？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter session set nls_date_format='yyyymmddhh24miss';  OR  可以在init.ora中加上一行  nls_date_format='yyyymmddhh24miss' &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;51.如何将小表放入keep池中？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter table xxx storage(buffer_pool keep); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;52.如何检查是否安装了某个patch？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;check that oraInventory &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;53.如何使select语句使查询结果自动生成序号？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select rownum,COL from table; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;54.如何知道数据裤中某个表所在的tablespace？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select tablespace_name from user_tables where table_name='TEST';  select * from user_tables中有个字段TABLESPACE_NAME，（oracle）;  select * from dba_segments where …; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;55.怎么在sqlplus下修改procedure？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select line,trim(text) t from user_source where name =’A’ order by line; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;56.怎样解除PROCEDURE被意外锁定？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter system kill session ,把那个session给杀掉，不过你要先查出她的session id  or  把该过程重新改个名字就可以了。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;57.SQL Reference是个什么东西？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;是一本sql的使用手册，包括语法、函数等等，oracle官方网站的文档中心有下载。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;58.如何查看数据库的状态？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;unix下  ps -ef | grep ora  windows下  看服务是否起来；  是否可以连上数据库。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;59.请问如何修改一张表的主键？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter table aaa  drop constraint aaa_key ;  alter table aaa  add constraint aaa_key primary key(a1,b1) ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;60.改变数据文件的大小？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;用 ALTER DATABASE .... DATAFILE .... ;  手工改变数据文件的大小，对于原来的 数据文件有没有损害。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;61.怎样查看ORACLE中有哪些程序在运行之中？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;查看v$sessions表。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;62.怎么可以看到数据库有多少个tablespace？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select * from dba_tablespaces; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;63.如何修改oracle数据库的用户连接数？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;修改initSID.ora，将process加大，重启数据库。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;64.如何查出一条记录的最后更新时间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;可以用logminer 察看。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;65.如何在PL/SQL中读写文件？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;UTL_FILE包允许用户通过PL/SQL读写操作系统文件。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;66.怎样把“&amp;amp;”放入一条记录中？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;insert into a values (translate ('at{&amp;amp;}t','at{}','at')); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;67.EXP　如何加ＱＵＥＲＹ参数？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;EXP USER/PASS FILE=A.DMP TABLES(BSEMPMS)  QUERY='&amp;quot;WHERE EMP_NO='S09394'&amp;quot; ； &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;68.关于oracle8i支持简体和繁体的字符集问题？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ZHS16GBK可以支持。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;69.Data Guard是什么软件？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;就是Standby的换代产品。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;70.如何创建SPFILE？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; connect / as sysdba  SQL&amp;gt; select * from v$version;  SQL&amp;gt; create pfile from spfile;  SQL&amp;gt; CREATE SPFILE FROM PFILE='E:ora9iadmineyglepfileinit.ora';  文件已创建。  SQL&amp;gt; CREATE SPFILE='E:ora9idatabaseSPFILEEYGLE.ORA' FROM PFILE='E:ora9iad  mineyglepfileinit.ora';  文件已创建。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;71.内核参数的应用？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;shmmax  含义：这个设置并不决定究竟Oracle数据库或者操作系统使用多少物理内存，只决定  了最多可以使用的内存数目。这个设置也不影响操作系统的内核资源。  设置方法：0.5*物理内存  例子：Set shmsys:shminfo_shmmax=10485760  shmmin  含义：共享内存的最小大小。  设置方法：一般都设置成为1。  例子：Set shmsys:shminfo_shmmin=1：  shmmni  含义：系统中共享内存段的最大个数。  例子：Set shmsys:shminfo_shmmni=100  shmseg  含义：每个用户进程可以使用的最多的共享内存段的数目。  例子：Set shmsys:shminfo_shmseg=20：  semmni  含义：系统中semaphore identifierer的最大个数。  设置方法：把这个变量的值设置为这个系统上的所有Oracle的实例的init.ora中的最  大的那个processes的那个值加10。  例子：Set semsys:seminfo_semmni=100  semmns  含义：系统中emaphores的最大个数。  设置方法：这个值可以通过以下方式计算得到：各个Oracle实例的initSID.ora里边的  processes的值的总和（除去最大的Processes参数）＋最大的那个Processes×2＋10×Or  acle实例的个数。  例子：Set semsys:seminfo_semmns=200  semmsl:  含义：一个set中semaphore的最大个数。  设置方法：设置成为10＋所有Oracle实例的InitSID.ora中最大的Processes的值。  例子：Set semsys:seminfo_semmsl=-200 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;72.怎样查看哪些用户拥有SYSDBA、SYSOPER权限？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;conn sys/change_on_install  SQL&amp;gt;select * from V_$PWFILE_USERS; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;73.如何单独备份一个或多个表？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;exp 用户/密码 tables=(表1,…,表2) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;74.如何单独备份一个或多个用户？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;exp system/manager owner=(用户1,用户2,…,用户n) file=导出文件 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;75.如何对CLOB字段进行全文检索？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM A WHERE dbms_lob.instr(a.a,'K',1,1)&amp;gt;0; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;76.如何显示当前连接用户？&lt;/p&gt; &lt;p&gt;SHOW USER&lt;/p&gt; &lt;p&gt;77.如何查看数据文件放置的路径 ？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;col file_name format a50  SQL&amp;gt; select tablespace_name,file_id,bytes/1024/1024,file_name from dba_data_fi  les order by file_id; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;78.如何查看现有回滚段及其状态 ？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; col segment format a30  SQL&amp;gt; SELECT SEGMENT_NAME,OWNER,TABLESPACE_NAME,SEGMENT_ID,FILE_ID,STATUS FROM  DBA_ROLLBACK_SEGS &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;79.如何改变一个字段初始定义的Check范围？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; alter table xxx drop constraint constraint_name;  之后再创建新约束:  SQL&amp;gt; alter table xxx add constraint constraint_name check(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;80.Oracle常用系统文件有哪些？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;通过以下视图显示这些文件信息：v$database,v$datafile,v$logfile v$controlfile v$  parameter; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;81.内连接INNER JOIN？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Select a.* from bsempms a,bsdptms b where a.dpt_no=b.dpt_no; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;82.如何外连接？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Select a.* from bsempms a,bsdptms b where a.dpt_no=b.dpt_no(+);  Select a.* from bsempms a,bsdptms b wherea.dpt_no(+)=b.dpt_no; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;83.如何执行脚本SQL文件？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;@$PATH/filename.sql; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;84.如何快速清空一个大表？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;truncate table table_name; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;85.如何查有多少个数据库实例？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;SELECT * FROM V$INSTANCE; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;86.如何查询数据库有多少表？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;select * from all_tables; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;87.如何测试SQL语句执行所用的时间？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;set timing on ;  SQL&amp;gt;select * from tablename; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;88.CHR()的反函数是？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ASCII()  SELECT CHAR(65) FROM DUAL;  SELECT ASCII('A') FROM DUAL; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;89.字符串的连接&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT CONCAT(COL1,COL2) FROM TABLE ;  SELECT COL1||COL2 FROM TABLE ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;90.怎么把select出来的结果导到一个文本文件中？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;SPOOL C:ABCD.TXT;  SQL&amp;gt;select * from table;  SQL &amp;gt;spool off; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;91.怎样估算SQL执行的I/O数？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;SET AUTOTRACE ON ;  SQL&amp;gt;SELECT * FROM TABLE;  OR  SQL&amp;gt;SELECT * FROM v$filestat ;  可以查看IO数。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;92.如何在sqlplus下改变字段大小？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter table table_name modify (field_name varchar2(100));  改大行，改小不行（除非都是空的）。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;93.如何查询某天的数据？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select * from table_name where trunc(日期字段)＝to_date('2003-05-02','yyyy-mm-dd'); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;94.sql 语句如何插入全年日期？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;create table BSYEAR (d date);  insert into BSYEAR  select to_date('20030101','yyyymmdd')+rownum-1  from all_objects   where rownum &amp;lt;= to_char(to_date('20031231','yyyymmdd'),'ddd'); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;95.如果修改表名？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter table old_table_name rename to new_table_name; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;96.如何取得命令的返回状态值？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sqlcode=0 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;97.如何知道用户拥有的权限？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT * FROM dba_sys_privs ; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;98.从网上下载的ORACLE9I与市场上卖的标准版有什么区别？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;从功能上说没有区别，只不过oracle公司有明文规定；从网站上下载的oracle产品不得用  于 商业用途，否则侵权。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;99.怎样判断数据库是运行在归档模式下还是运行在非归档模式下？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;进入dbastudio，历程--〉数据库---〉归档查看。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;100.sql&amp;gt;startup pfile和ifile,spfiled有什么区别？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pfile就是Oracle传统的初始化参数文件，文本格式的。  ifile类似于c语言里的include，用于把另一个文件引入  spfile是9i里新增的并且是默认的参数文件，二进制格式  startup后应该只可接pfile。 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 04 Jan 2018 09:00:47 GMT</pubDate>
    </item>
    <item>
      <title>Oracle用户和表被锁定解决方法</title>
      <link>https://maruifu.cn/article/70</link>
      <content:encoded>&lt;p&gt;1、用dba角色的用户登陆，进行解锁，先设置具体时间格式，以便查看具体时间&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'; Session altered. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2、查看具体的被锁时间&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; select username,lock_date from dba_users where username='TEST'; USERNAME LOCK_DATE TEST 2009-03-10 08:51:03 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3、解锁&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt; alter user test account unlock; User altered. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4、查看是那个ip造成的test用户被锁&lt;/p&gt; &lt;pre&gt;&lt;code&gt;查看$ORACLE_HOME/network/admin/log/listener.log日志 10-MAR-2009 08:51:03 * (CONNECT_DATA=(SID=lhoms)(SERVER=DEDICATED)(CID=(PROGRAM=oracle)(HOST=omstestdb)(USER=oraoms))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.69.1.11)(PORT=49434)) * establish * lhoms * 0 10-MAR-2009 08:51:03 * (CONNECT_DATA=(SID=lhoms)(SERVER=DEDICATED)(CID=(PROGRAM=oracle)(HOST=omstestdb)(USER=oraoms))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.69.1.11)(PORT=49435)) * establish * lhoms * 0 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这样可知是上面10.69.1.11的ip尝试多次失败登陆造成的被锁 注： 一般数据库默认是10次尝试失败后锁住用户&lt;/p&gt; &lt;p&gt;1、查看FAILED_LOGIN_ATTEMPTS的值&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select * from dba_profiles where RESOURCE_NAME = 'FAILED_LOGIN_ATTEMPTS'; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2、修改为30次&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter profile default limit FAILED_LOGIN_ATTEMPTS 30; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3、修改为无限次（为安全起见，不建议使用）&lt;/p&gt; &lt;pre&gt;&lt;code&gt;alter profile default limit FAILED_LOGIN_ATTEMPTS unlimited; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Oracle数据库操作中，我们有时会用到锁表查询以及解锁和kill进程等操作，那么这些操作是怎么实现的呢？本文我们主要就介绍一下这部分内容。&lt;/p&gt; &lt;p&gt;(1)锁表查询的代码有以下的形式：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select count(*) from v$locked_object; select * from v$locked_object; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;(2)查看哪个表被锁&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select b.owner,b.object_name,a.session_id,a.locked_mode from v$locked_object a,dba_objects b where b.object_id = a.object_id; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;(3)查看是哪个session引起的&lt;/p&gt; &lt;pre&gt;&lt;code&gt;select b.username,b.sid,b.serial#,logon_time from v$locked_object a,v$session b where a.session_id = b.sid order by b.logon_time;  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;(4)杀掉对应进程&lt;/p&gt; &lt;p&gt;执行命令：&lt;code&gt;alter system kill session'1025,41';&lt;/code&gt; 其中1025为sid,41为serial#.&lt;/p&gt; &lt;p&gt;查询那个程序导致的&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SELECT v.program,v.* FROM v$session v where serial# = '9' and sid =  586 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 27 Dec 2017 09:02:53 GMT</pubDate>
    </item>
    <item>
      <title>Oracle用户密码过期的处理方法</title>
      <link>https://maruifu.cn/article/69</link>
      <content:encoded>&lt;p&gt;受影响版本：Oracle11g以上版本。&lt;/p&gt; &lt;p&gt;导致密码消失的原因：Oracle 11g中默认的DEFAULT概要文件中口令有效期PASSWORD_LIFE_TIME默认值为180天。&lt;/p&gt; &lt;p&gt;当以客户端登陆Oracle提示ORA-28002，则基本可以确定登陆帐号已过有效期，使用具有DBA权限的帐号重置该帐号密码即可。&lt;/p&gt; &lt;p&gt;解决方法：&lt;/p&gt; &lt;p&gt;以下步骤以具有DBA权限用户操作&lt;/p&gt; &lt;h2&gt;1.查看口令失效用户的profile文件&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;SELECT username,profile FROM dba_users; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;EM：服务器&amp;gt;用户，查看口令失效的用户对应的概要文件，这里假设为DEFAULT，下同。&lt;/p&gt; &lt;h2&gt;2.查看对应的概要文件的口令有效期设置&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;SELECT * FROM dba_profiles WHERE profile='DEFAULT' AND resource_name='PASSWORD_LIFE_TIME'; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;EM：服务器&amp;gt;概要文件&amp;gt;选择刚刚查到的概要文件DEFAULT&amp;gt;查看，查看口令下面的有效期值。&lt;/p&gt; &lt;h2&gt;3.将口令有效期默认值180天修改成“无限制”&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;EM：服务器&amp;gt;概要文件&amp;gt;选择刚刚查到的概要文件DEFAULT&amp;gt;编辑&amp;gt;口令，在有效期输入或选择你需要的值，保存。&lt;/p&gt; &lt;p&gt;该参数修改实时生效。&lt;/p&gt; &lt;p&gt;出于数据库安全性考虑，不建议将PASSWORD_LIFE_TIME值设置成UNLIMITED，即建议客户能够定期修改数据库用户口令。&lt;/p&gt; &lt;p&gt;在修改PASSWORD_LIFE_TIME值之前已经失效的用户，还是需要重新修改一次密码才能使用。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SQL&amp;gt;alter user  user_name identified by password; &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 27 Dec 2017 08:53:13 GMT</pubDate>
    </item>
    <item>
      <title>HotSpot(热点最新的一种的java虚拟机)详解</title>
      <link>https://maruifu.cn/article/68</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;java虚拟机热点代码检测什么鬼?&lt;/p&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h2&gt;1. HotSpot历史&lt;/h2&gt; &lt;p&gt;SUN的JDK版本从1.3.1开始运用HotSpot虚拟机， 2006年底开源，主要使用C++实现，JNI接口部分用C实现。&lt;/p&gt; &lt;p&gt;HotSpot是较新的Java虚拟机，用来代替JIT(Just in Time)，可以大大提高Java运行的性能。&lt;/p&gt; &lt;p&gt;Java原先是把源代码编译为字节码在虚拟机执行，这样执行速度较慢。而HotSpot将常用的部分代码编译为本地(原生，native)代码，这样显着提高了性能。 HotSpot JVM 参数可以分为规则参数(standard options)和非规则参数(non-standard options)。&lt;/p&gt; &lt;p&gt;规则参数相对稳定，在JDK未来的版本里不会有太大的改动。 非规则参数则有因升级JDK而改动的可能。 规则和非规则参数这里不做介绍了，网上资料很多。&lt;/p&gt; &lt;h2&gt;2.HotSpot基础知识&lt;/h2&gt; &lt;p&gt;HotSpot包括一个解释器和两个编译器（client 和 server，二选一的），解释与编译混合执行模式，默认启动解释执行。&lt;/p&gt; &lt;p&gt;编译器：java源代码被编译器编译成class文件（字节码），java字节码在运行时可以被动态编译（JIT）成本地代码(前提是解释与编译混合执行模式且虚拟机不是刚启动时)。&lt;/p&gt; &lt;p&gt;解释器： 解释器用来解释class文件（字节码），java是解释语言（书上这么说的）。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;server启动慢，占用内存多，执行效率高，适用于服务器端应用； client启动快，占用内存小，执行效率没有server快，默认情况下不进行动态编译，适用于桌面应用程序。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;由-XX:+RewriteFrequentPairs参数控制  client模式默认关闭，server模式默认开启&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;在jre安装目录下的lib/i386/jvm.cfg 文件下。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;pre&gt;&lt;code&gt;java -version Java HotSpot(TM) Client VM (build 14.3-b01, mixed mode, sharing) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;mixed mode 解释与编译 混合的执行模式 默认使用这种模式&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -Xint -version Java HotSpot(TM) Client VM (build 14.3-b01, interpreted mode, sharing) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;interpreted  纯解释模式 禁用JIT编译&lt;/p&gt; &lt;pre&gt;&lt;code&gt;java -Xcomp -version Java HotSpot(TM) Client VM (build 14.3-b01, compiled mode, sharing) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;compiled  纯编译模式（如果方法无法编译，则回退到解释模式执行无法编译的方法）&lt;/p&gt; &lt;h2&gt;3.动态编译&lt;/h2&gt; &lt;p&gt;动态编译(compile during run-time)，英文称Dynamic compilation；Just In Time也是这个意思。&lt;/p&gt; &lt;p&gt;HotSpot对bytecode的编译不是在程序运行前编译的，而是在程序运行过程中编译的。&lt;/p&gt; &lt;p&gt;HotSpot里运行着一个监视器（Profile Monitor），用来监视程序的运行状况。 java字节码（class文件）是以解释的方式被加载到虚拟机中(默认启动时解释执行)。 程序运行过程中，那一部分运用频率大，那些对程序的性能影响重要。对程序运行效率影响大的代码，称为热点（hotspot），HotSpot会把这些热点动态地编译成机器码（native code），同时对机器码进行优化，从而提高运行效率。对那些较少运行的代码，HotSpot就不会把他们编译。&lt;/p&gt; &lt;p&gt;HotSpot对字节码有三层处理：不编译(字节码加载到虚拟机中时的状态。也就是当虚拟机执行的时候再编译)，编译(把字节码编译成本地代码。虚拟机执行的时候已经编译好了，不要再编译了)，编译并优化（不但把字节码编译成本地代码，而且还进行了优化）。&lt;/p&gt; &lt;p&gt;至于那些程序那些不编译，那些编译，那些优化，则是由监视器（Profile Monitor）决定。&lt;/p&gt; &lt;h2&gt;4.为什么不静态编译那？&lt;/h2&gt; &lt;p&gt;为什么字节码在装载到虚拟机之前就编译成本地代码那？&lt;/p&gt; &lt;p&gt;动态编译器也在许多方面比静态编译器优越。静态编译器通常很难准确预知程序运行过程中究竟什么部分最需要优化。&lt;/p&gt; &lt;p&gt;函数调用都是很浪费系统时间的，因为有许多进栈出栈操作。因此有一种优化办法，就是把原来的函数调用，通过编译器的编译，改成非函数调用，把函数代码直接嵌到调用出，变成顺序执行。&lt;/p&gt; &lt;p&gt;面向对象的语言支持多态，静态编译无效确定程序调用哪个方法，因为多态是在程序运行中确定调用哪个方法。&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 27 Dec 2017 00:39:34 GMT</pubDate>
    </item>
    <item>
      <title>公司内网网线，外网wifi解决办法!</title>
      <link>https://maruifu.cn/article/67</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;同时使用内外网不用切换这么流的操作你不会不学一下?&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;公司搬家了,原来连接一个网络可以同时上内外网,现在,内网是内网,外网是外网,来回切换,工作起来很费劲,现在要不切换就同时使用内外网,不过发现这样有个问题，就是在笔记本的无线连上外网的时候，只要插上内网的网线，就发现qq一类的一律掉线，外网连接就不行了，网页也打不开。这样搞的比较麻烦，每次测试完程序，要手动拔掉网线，外网才能上。感觉这样的做法太麻烦了，能否内网和外网同时接入到笔记本，不用每次拔网线呢？于是在网上找了找资料，果然还找到了。其实是因为Windows默认路由设置的问题。Windows每次会默认把对所有IP的访问，指向以太网或者无线网的网关，当同时接入两个、或者多个连接时，这样的默认路由会有问题，需要我们手动修改一下。&lt;/p&gt; &lt;p&gt;有两个方法:我用的方法2&lt;/p&gt; &lt;p&gt;方法一:&lt;/p&gt; &lt;p&gt;这个是可以设置的，先链接有线网络，不知道你们的IP是不是自动获取，我的是静态。此时是可以链接内网资源。然后打开无线网络连接，链接到AP以后，发现不能上网，此时进入“ 网络和共享中心” 进入“适配器设置”点击“组织”那里，选择“布局”把 菜单栏调出来，选择“高级”“高级设置”。在这里把无线网络连接的顺序调到最前面。  接下来就可以上外网了，并且内网访问也正常。  目前这个方法存在一个缺点，就是当无线设备关闭以后，再重启。发现再打开无线设备，原先的设置都还原了，需要重新设置。&lt;/p&gt; &lt;p&gt;方法二:&lt;/p&gt; &lt;p&gt;切记: 先连接有线网络,把IPV4地址 记住 我的是 &lt;code&gt;10.18.141.254&lt;/code&gt;&lt;/p&gt; &lt;p&gt;再连接wifi内网,我的以太网的ipv4默认网关给关掉了，记住以太网的默认网关.&lt;/p&gt; &lt;p&gt;用管理员权限运行cmd，输入 &lt;code&gt;route print&lt;/code&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;============================================================== 接口列表  17...60 6d c7 6a bb 15 ......Realtek RTL8723BE Wireless LAN 8  22...00 ff 93 fc eb 96 ......TAP-Windows Adapter V9  14...f0 76 1c e1 a9 74 ......Realtek PCIe GBE Family Controll  12...60 6d c7 6a bb 16 ......Bluetooth 设备(个人区域网)   1...........................Software Loopback Interface 1 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我链接网线的网卡是Realtek的网卡，接口序号是14 另外，往下拉，看IPv4路由表这一项：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;IPv4 路由表 =========================================================================== 活动路由: 网络目标        网络掩码          网关              接口        跃点数 0.0.0.0        0.0.0.0       20.18.0.254      20.18.0.194     55 10.0.0.0       255.0.0.0     10.18.141.254    10.18.141.12    39 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果10开头的网关和20开头的网关在“网络目标”和“网络掩码”的地址一样的话，需要用如下命令清理路由：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;route delete 0.0.0.0 10.18.141.254 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;接着在cmd输入如下命令：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;route add -p 10.0.0.0 mask 255.0.0.0 10.18.141.254 if 14   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其中10.18.141.254是ipv4的网关 其中最后的14是你连接网线的网卡的接口序号，之后就可以访问内外网了~&lt;/p&gt; &lt;p&gt;清空路由： 某些情况下，路由配置可能影响机器问题，需要路由清空的话，请执行以下命令：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;route delete 0.0.0.0 route delete 10.0.0.0 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后重启电脑&lt;/p&gt; &lt;p&gt;已知bug： 更换网络环境后，部分在本地启动的服务会启动失败，重现不能。清空路由后现象消失&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 20 Dec 2017 06:08:00 GMT</pubDate>
    </item>
    <item>
      <title>数据结构的特性</title>
      <link>https://maruifu.cn/article/66</link>
      <content:encoded>&lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="center"&gt;数据结构&lt;/th&gt;&lt;th&gt;优点&lt;/th&gt;&lt;th&gt;缺点&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="center"&gt;数组&lt;/td&gt;&lt;td&gt;插入快,如果知道下标,可以非常快地存取&lt;/td&gt;&lt;td&gt;查找慢,删除慢,大小固定&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;有序数组&lt;/td&gt;&lt;td&gt;比无序的数组查找快&lt;/td&gt;&lt;td&gt;删除和插入慢,大小固定&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;栈&lt;/td&gt;&lt;td&gt;提供后进先出的方式存取&lt;/td&gt;&lt;td&gt;存取其他项很慢&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;队列&lt;/td&gt;&lt;td&gt;提供先进先出方式的存取&lt;/td&gt;&lt;td&gt;存取其他项很慢&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;链表&lt;/td&gt;&lt;td&gt;插入快,删除快&lt;/td&gt;&lt;td&gt;查找慢&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;二叉树&lt;/td&gt;&lt;td&gt;查找,插入,删除都快(如果树保持平衡)&lt;/td&gt;&lt;td&gt;删除算法复杂&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;红-黑 树&lt;/td&gt;&lt;td&gt;查找,插入,删除都很快.树总是平衡的&lt;/td&gt;&lt;td&gt;算法复杂&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;2-3-4 树&lt;/td&gt;&lt;td&gt;查找,插入,删除都很快.树总是平衡的. 类似树对磁盘存储有用&lt;/td&gt;&lt;td&gt;算法复杂&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;哈希表&lt;/td&gt;&lt;td&gt;如果关键字已知则存取极快,插入快.&lt;/td&gt;&lt;td&gt;删除慢,如果不知道关键字则存取很慢, 对存储空间使用不充分.&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;堆&lt;/td&gt;&lt;td&gt;插入,删除快,对最大数据项的存取很快&lt;/td&gt;&lt;td&gt;存取其他数据项慢&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;图&lt;/td&gt;&lt;td&gt;对现实世界建模&lt;/td&gt;&lt;td&gt;有些算法慢且复杂&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Tue, 12 Dec 2017 07:50:09 GMT</pubDate>
    </item>
    <item>
      <title>数据结构和算法关系</title>
      <link>https://maruifu.cn/article/65</link>
      <content:encoded>&lt;p&gt;数据结构：数据与数据之间的结构关系（数组、队列、树、图等结构）&lt;/p&gt; &lt;p&gt;算法：解决问题的步骤&lt;/p&gt; &lt;p&gt;总结：&lt;/p&gt; &lt;p&gt;1、程序 = 数据结构 + 算法 。数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿了整个程序世界，首先二者表现为不可分割的关系。没有数据间的有机关系，程序根本无法设计。&lt;/p&gt; &lt;p&gt;2、数据结构与算法关系：数据结构是底层，算法高层。数据结构为算法提供服务。算法围绕数据结构操作。&lt;/p&gt; &lt;p&gt;3、解决问题（算法）需要选择正确的数据结构。例如：算法中经常需要对数据进行增加和删除用链表数据结构效率高，数组数据结构因为增加和删除需要移动数字每个元素所有效率低。&lt;/p&gt; &lt;p&gt;4、数据结构特点：每种数据结构都具有自己的特点。例如：队列：先进先出。栈：先进后出。等等&lt;/p&gt; &lt;p&gt;5、算法的特性：算法具有五个基本特征：输入、输出、有穷性、确定性和可行性。&lt;/p&gt; &lt;p&gt;6、数据结构应用：数据结构往往同高效的检索算法、索引技术、排序算法有关&lt;/p&gt; &lt;p&gt;7、数据结构（逻辑数据结构）通过计算机语言来实现数据结构（存储数据结构）。例如：树型数据结构：通过计算机语言中的数组（节点）和指针（指向父节点）来实现。&lt;/p&gt; &lt;p&gt;8、存储结构：逻辑数据结构的实现。存储结构通过计算机语言实现。  例如：堆数据结构，堆是一棵完全二叉树，所以适宜采用顺序存储结构（顺序存储：数组），这样能够充分利用存储空间。&lt;/p&gt; &lt;p&gt;9、算法目的：算法是为数据结构服务。例如：数据结构通常伴随有查找算法、排序算法等&lt;/p&gt; &lt;p&gt;10、数据结构的优劣：一种数据结构的优劣是在实现其各种运算的算法中体现的。&lt;/p&gt; &lt;p&gt;二、数据结构：分为逻辑数据结构和存储数据结构两种 （1）顺序存储方法（顺序存储结构） （2）链接存储方法（链式存储结构） 同一种逻辑结构可采用不同的存储方法（以上两种之一或组合），这主要考虑的是运算方便及算法的时空要求。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 12 Dec 2017 07:32:52 GMT</pubDate>
    </item>
    <item>
      <title>秒杀系统架构优化思路</title>
      <link>https://maruifu.cn/article/64</link>
      <content:encoded>&lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;h2&gt;一、为什么难&lt;/h2&gt; &lt;p&gt;秒杀系统难做的原因：库存只有一份，所有人会在集中的时间读和写这些数据。 例如小米手机每周二的秒杀，可能手机只有1万部，但瞬时进入的流量可能是几百几千万。 又例如12306抢票，亦与秒杀类似，瞬时流量更甚。&lt;/p&gt; &lt;h2&gt;二、常见架构&lt;/h2&gt; &lt;p&gt;流量到了亿级别，常见站点架构如上：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;1）浏览器端，最上层，会执行到一些JS代码&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;2）站点层，这一层会访问后端数据，拼html页面返回给浏览器&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;3）服务层，向上游屏蔽底层数据细节&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;4）数据层，最终的库存是存在这里的，mysql是一个典型&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;三、优化方向&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;1）将请求尽量拦截在系统上游：传统秒杀系统之所以挂，请求都压倒了后端数据层，数据读写锁冲突严重，并发高响应慢，几乎所有请求都超时，流量虽大，下单成功的有效流量甚小【一趟火车其实只有2000张票，200w个人来买，基本没有人能买成功，请求有效率为0】&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;2）充分利用缓存：这是一个典型的读多些少的应用场景【一趟火车其实只有2000张票，200w个人来买，最多2000个人下单成功，其他人都是查询库存，写比例只有0.1%，读比例占99.9%】，非常适合使用缓存&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;四、优化细节&lt;/h2&gt; &lt;h3&gt;浏览器层请求拦截&lt;/h3&gt; &lt;p&gt;点击了“查询”按钮之后，系统那个卡呀，进度条涨的慢呀，作为用户，会不自觉的再去点击“查询”，继续点，继续点，点点点。。。有用么？平白无故的增加了系统负载（一个用户点5次，80%的请求是这么多出来的），怎么整？&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;a）产品层面，用户点击“查询”或者“购票”后，按钮置灰，禁止用户重复提交请求&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;b）JS层面，限制用户在x秒之内只能提交一次请求 如此限流，80%流量已拦&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;站点层请求拦截与页面缓存&lt;/h3&gt; &lt;p&gt;浏览器层的请求拦截，只能拦住小白用户（不过这是99%的用户哟），高端的程序员根本不吃这一套，写个for循环，直接调用你后端的http请求，怎么整？&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;a）同一个uid，限制访问频度，做页面缓存，x秒内到达站点层的请求，均返回同一页面&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;b）同一个item的查询，例如手机车次，做页面缓存，x秒内到达站点层的请求，均返回同一页面 如此限流，又有99%的流量会被拦截在站点层&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;服务层请求拦截与数据缓存&lt;/h3&gt; &lt;p&gt;站点层的请求拦截，只能拦住普通程序员，高级黑客，假设他控制了10w台肉鸡（并且假设买票不需要实名认证），这下uid的限制不行了吧？怎么整？&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;a）大哥，我是服务层，我清楚的知道小米只有1万部手机，我清楚的知道一列火车只有2000张车票，我透10w个请求去数据库有什么意义呢？对于写请求，做请求队列，每次只透过有限的写请求去数据层，如果均成功再放下一批，如果库存不够则队列里的写请求全部返回“已售完”&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;b）对于读请求，还用说么？cache来抗，不管是memcached还是redis，单机抗个每秒10w应该都是没什么问题的 如此限流，只有非常少的写请求，和非常少的读缓存mis的请求会透到数据层去，又有99.9%的请求被拦住了&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;数据层闲庭信步&lt;/h3&gt; &lt;p&gt;到了数据这一层，几乎就没有什么请求了，单机也能扛得住，还是那句话，库存是有限的，小米的产能有限，透过过多请求来数据库没有意义。&lt;/p&gt; &lt;h2&gt;五、总结&lt;/h2&gt; &lt;p&gt;没什么总结了，上文应该描述的非常清楚了，对于秒杀系统，再次重复下笔者的两个架构优化思路：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;1）尽量将请求拦截在系统上游&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;2）读多写少的常用多使用缓存&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt;</content:encoded>
      <pubDate>Tue, 12 Dec 2017 02:18:15 GMT</pubDate>
    </item>
    <item>
      <title>百度怎么做长文本去重的?</title>
      <link>https://maruifu.cn/article/63</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;（1）原创不易，互联网抄袭成风，很多原创内容在网上被抄来抄去，改来改去&lt;/p&gt; &lt;p&gt;（2）百度的网页库非常大，爬虫如何判断一个新网页是否与网页库中已有的网页重复呢？&lt;/p&gt; &lt;p&gt;这是本文要讨论的问题（尽量用大家都能立刻明白的语言和示例表述）。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;一、传统签名算法与文本完整性判断&lt;/h2&gt; &lt;h3&gt;问题抛出：&lt;/h3&gt; &lt;p&gt;（1）运维上线一个bin文件，将文件分发到4台线上机器上，如何判断bin文件全部是一致的？ （2）用户A将消息msg发送给用户B，用户B如何判断收到的msg_t就是用户A发送的msg？&lt;/p&gt; &lt;h3&gt;思路：&lt;/h3&gt; &lt;p&gt;一个字节一个字节的比对两个大文件或者大网页效率低，我们可以用一个签名值（例如md5值）代表一个大文件，签名值相同则认为大文件相同（先不考虑冲突率）&lt;/p&gt; &lt;h3&gt;回答：&lt;/h3&gt; &lt;p&gt;（1）将bin文件取md5，将4台线上机器上的bin文件也取md5，如果5个md5值相同，说明一致 （2）用户A将msg以及消息的md5同时发送给用户B，用户B收到msg_t后也取md5，得到的值与用户A发送过来的md5值如果相同，则说明msg_t与msg相同&lt;/p&gt; &lt;h3&gt;结论：&lt;/h3&gt; &lt;p&gt;md5是一种签名算法，常用来判断数据的完整性与一致性&lt;/p&gt; &lt;p&gt;md5设计原则：两个文本哪怕只有1个bit不同，其md5签名值差别也会非常大，故它只适用于“完整性”check，不适用于“相似性”check。&lt;/p&gt; &lt;h3&gt;新问题抛出：&lt;/h3&gt; &lt;p&gt;有没有一种签名算法，如果文本非常相似，签名值也非常相似呢？&lt;/p&gt; &lt;h2&gt;二、文本相似性的签名算法&lt;/h2&gt; &lt;p&gt;上文提出的问题，可以用局部敏感哈希LSH（Locality Sensitive Hash）解决，局部敏感哈希是一类文本越相似，哈希值越相似的hash算法，有兴趣的同学自行百度，这里分享一下minHash的思路。&lt;/p&gt; &lt;h3&gt;问题的提出：什么是minHash？&lt;/h3&gt; &lt;p&gt;回答：minHash是局部敏感哈希的一种，它常用来快速判定集合的相似性，也常用于检测网页的重复性，其思路为，用相同的规则抽取集合中的少部分元素代表整个集合，如果少部分元素的重合度很高，非常可能整个集合的重复度也很高。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;举例：待判定的集合为A{1, 7, 5, 9, 3, 11, 15, 13} 已有的集合为： B{10, 8, 2, 4, 6, 0, 1, 16}, C{100, 700, 500, 900, 300, 1100, 1500,1300}, D{1, 3, 2, 4, 6, 5, 8, 7} 假设使用部分元素代替全体集合的规则为：集合内元素进行排序，取值最小的4个（这个过程有信息损失，我们可以认为是一个hash过程） 处理结果为： A{1, 3, 5, 7} B{0, 1, 2, 4}  =&amp;gt; A与B有1个元素相同 C{100, 300, 500, 700}  =&amp;gt; A与C有0个元素相同 D{1, 2, 3, 4}  =&amp;gt; A与D有2个元素相同 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;判断结论：我们认为集合A与集合D是最相似的&lt;/p&gt; &lt;p&gt;这个例子有点2，但基本能说明整体思路，实际在执行的过程中： （1）我们可以使用更多的元素来代表集合，以提高准确性（例如，将上例中的4个元素代表集合升级为8个元素代表集合） （2）我们可以使用更多的hash函数来代表集合，以提高准确性（例如，上例除了“排序后取值最小的4个元素代表集合”，还可以增加一个哈希函数“排序后取值最大的4个元素代表集合”） （3）minHash可以量化评判相似度，亦可以评判网页是否重复（一个分类问题），设定相似度阈值，高于阈值为重复，低于阈值为不重复 （4）实际排重过程中，网页库中的哈希值都可以提前计算，只有待判定的集合或者网页的哈希值需要临时计算&lt;/p&gt; &lt;h2&gt;三、minHash与长文本重复度检测有什么关系&lt;/h2&gt; &lt;p&gt;目前看来没什么关系，但如果我们能将每一个长文本用一个集合来表示，就能将长文本的相似度用minHash来解决了。&lt;/p&gt; &lt;h3&gt;问题的提出 ：&lt;/h3&gt; &lt;p&gt;如何将长文本转化为集合？&lt;/p&gt; &lt;h3&gt;回答：&lt;/h3&gt; &lt;p&gt;我去，分词不是就可以么&lt;/p&gt; &lt;pre&gt;&lt;code&gt;举例：待判定的长文本为A{我是清河小马哥，我来自河北清河} 已有网页库集合为： B{我是一只来自河北的狼} C{河北清河，羊绒之都} D{这事和我没关系，我是凑数的} 使用分词将上述文本集合化： A{我，清河，小马哥，来自，河北} B{我，河北，来自，狼} C{清河，羊绒，之都} D{事，我，凑数，关系} &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;判断结论：&lt;/h3&gt; &lt;p&gt;当当当当，转化为集合后，可以快速判断A与B的相似度最高，当然实际执行过程中，除了分词还得考虑词频，用这种方法对长文本进行相似度检测，准确率非常高（文本越长越准）&lt;/p&gt; &lt;h2&gt;四、还有没有更有效的方法&lt;/h2&gt; &lt;p&gt;使用上述方法进行文本相似度检测，需要进行中文分词，词频统计，哈希值计算，相似度计算，计算量微大。 然而，抄袭成风，一字不改的风气，让技术有了更广阔的优化空间，赞！ 怎么优化呢？ 不再进行分词，而是进行“分句”，用标点符号把长文按照句子分开，使用N个句子集合（例如一篇文章中5条最长的句子作为签名，注意，长句子比短句子更具有区分性）作为文章的签名，在抄袭成风的互联网环境下，此法判断网页的重复度能大大降低工程复杂度，并且准确度也异常的高。&lt;/p&gt; &lt;h2&gt;五、结论&lt;/h2&gt; &lt;p&gt;在抄袭成风的互联网环境下，采用“分句”的方式，用5条最长的网页内容作为网页的签名，能够极大的降低排重系统复杂度，提高排重准确率，不失为一种好的选择。 标题只是噱头，百度是不是这么做的我并不知道，知情的同学说一下哈。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 12 Dec 2017 02:06:27 GMT</pubDate>
    </item>
    <item>
      <title>maven编码</title>
      <link>https://maruifu.cn/article/62</link>
      <content:encoded>&lt;pre&gt;&lt;code&gt;  &amp;lt;properties&amp;gt;         &amp;lt;!-- 所有工程中,对依赖的根组件(root jar)的版本维护 --&amp;gt;         &amp;lt;java.version&amp;gt;1.8&amp;lt;/java.version&amp;gt;           &amp;lt;!-- 统一字符集 --&amp;gt;      &amp;lt;project.build.sourceEncoding&amp;gt;UTF-8&amp;lt;/project.build.sourceEncoding&amp;gt;    &amp;lt;/properties&amp;gt;                            &amp;lt;build&amp;gt;        &amp;lt;finalName&amp;gt;springBoot&amp;lt;/finalName&amp;gt;        &amp;lt;plugins&amp;gt;         &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;              &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;              &amp;lt;version&amp;gt;3.3&amp;lt;/version&amp;gt;              &amp;lt;configuration&amp;gt;                  &amp;lt;source&amp;gt;${java.version}&amp;lt;/source&amp;gt;                  &amp;lt;target&amp;gt;${java.version}&amp;lt;/target&amp;gt;                  &amp;lt;encoding&amp;gt;${project.build.sourceEncoding}&amp;lt;/encoding&amp;gt;              &amp;lt;/configuration&amp;gt;        &amp;lt;/plugins&amp;gt;    &amp;lt;/build&amp;gt;  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 01 Dec 2017 08:51:14 GMT</pubDate>
    </item>
    <item>
      <title>一个程序员该记住的话</title>
      <link>https://maruifu.cn/article/61</link>
      <content:encoded>&lt;p&gt;&lt;strong&gt;勒布朗（LeBlanc）法则：&lt;/strong&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;                  稍后等于永不（Later equals never） &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Dave  Thomas，OTI公司创始人，Eclipse战略教父说过:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;没有测试的代码不干净。不管它有多优雅，不管有多可读、多易理解，微乎测试，其不洁亦可知也。 &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 28 Nov 2017 11:37:00 GMT</pubDate>
    </item>
    <item>
      <title>传统 for 循环的函数式替代方案</title>
      <link>https://maruifu.cn/article/60</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;尽管 for 循环包含许多可变部分，但许多开发人员仍非常熟悉它，并会不假思索地使用它。从 Java™ 8 开始，我们有多个强大的新方法可帮助简化复杂迭代。在本文中，您将了解如何使用 IntStream 方法 range、iterate 和 limit 来迭代范围和跳过范围中的值。您还将了解新的 takeWhile 和 dropWhile 方法（即将在 Java 9 中引入）。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;for 循环的麻烦&lt;/h2&gt; &lt;p&gt;在 Java 语言的第 1 个版本中就开始引入了传统的 for 循环，它的更简单的变体 for-each 是在 Java 5 中引入的。大部分开发人员更喜欢使用 for-each 执行日常迭代，但对于迭代一个范围或跳过范围中的值等操作，他们仍会使用 for。&lt;/p&gt; &lt;p&gt;or 循环非常强大，但它包含太多可变部分。甚至在打印 get set 提示的最简单任务中，也可以看出这一点：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 1. 完成一个简单任务的复杂代码:  System.out.print(&amp;quot;Get set...&amp;quot;);   for(int i = 1; i &amp;lt; 4; i++) {     System.out.print(i + &amp;quot;...&amp;quot;);   } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在清单 1 中，我们从 1 开始循环处理索引变量 i，将它限制到小于 4 的值。请注意，for 循环需要我们告诉循环是递增的。在本例中，我们还选择了前递增而不是后递增。&lt;/p&gt; &lt;p&gt;清单 1 中没有太多代码，但比较繁琐。Java 8 提供了一种更简单、更优雅的替代方法：IntStream 的 range 方法。以下是打印清单 1 中的相同 get set 提示的 range方法：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 2. 完成一个简单任务的简单代码:  System.out.print(&amp;quot;Get set...&amp;quot;);   IntStream.range(1, 4)     .forEach(i -&amp;gt; System.out.print(i + &amp;quot;...&amp;quot;)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在清单 2 中，我们看到并没有显著减少代码量，但降低了它的复杂性。这样做有两个重要原因：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;不同于 for，range 不会强迫我们初始化某个可变变量。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;迭代会自动执行，所以我们不需要像循环索引一样定义增量。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;在语义上，最初的 for 循环中的变量 i 是一个可变变量。理解 range 和类似方法的价值对理解该设计的结果很有帮助。&lt;/p&gt; &lt;h2&gt;可变变量与参数&lt;/h2&gt; &lt;p&gt;for 循环中定义的变量 i 是单个变量，它会在每次对循环执行迭代时发生改变。range 示例中的变量 i 是Lambda表达式的参数，所以它在每次迭代中都是一个全新的变量。这是一个细微区别，但决定了两种方法的不同。以下示例有助于阐明这一点。&lt;/p&gt; &lt;p&gt;清单 3 中的 for 循环想在一个内部类中使用索引变量：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 3. 在内部类中使用索引变量:  ExecutorService executorService = Executors.newFixedThreadPool(10);         for(int i = 0; i &amp;lt; 5; i++) {         int temp = i;           executorService.submit(new Runnable() {           public void run() {             //If uncommented the next line will result in an error             //System.out.println(&amp;quot;Running task &amp;quot; + i);              //local variables referenced from an inner class must be final or effectively final               System.out.println(&amp;quot;Running task &amp;quot; + temp);            }         });       }         executorService.shutdown(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们有一个匿名的内部类实现了 Runnable 接口。我们想在 run 方法中访问索引变量 i，但编译器不允许这么做。&lt;/p&gt; &lt;p&gt;作为此限制的解决办法，我们可以创建一个局部临时变量，比如 temp，它是索引变量的一个副本。每次新的迭代都会创建变量 temp。在 Java 8 以前，我们需要将该变量标记为 final。从 Java 8 开始，可以将它视为实际的最终结果，因为我们不会再更改它。无论如何，由于事实上索引变量是一个在迭代中改变的变量，for 循环中就会出现这个额外变量。&lt;/p&gt; &lt;p&gt;现在尝试使用 range 函数解决同一个问题。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 4. 在内部类中使用Lambda参数:  ExecutorService executorService = Executors.newFixedThreadPool(10);                              IntStream.range(0, 5)        .forEach(i -&amp;gt;           executorService.submit(new Runnable() {            public void run() {              System.out.println(&amp;quot;Running task &amp;quot; + i);             }          }));        executorService.shutdown(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在作为一个参数被Lambda表达式接受后，索引变量 i 的语义与循环索引变量有所不同。与清单 3 中手动创建的 temp 非常相似，这个 i 参数在每次迭代中都表现为一个全新的变量。它是实际最终变量，因为我们不会在任何地方更改它的值。因此，我们可以直接在内部类的上下文中使用它 — 且不会有任何麻烦。&lt;/p&gt; &lt;p&gt;因为 Runnable 是一个函数接口，所以我们可以轻松地将匿名的内部类替换为Lambda表达式，比如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 5. 将内部类替换为Lambda表达式:  IntStream.range(0, 5)        .forEach(i -&amp;gt;           executorService.submit(() -&amp;gt; System.out.println(&amp;quot;Running task &amp;quot; + i))); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;显然，对于相对简单的迭代，使用 range 代替 for 具有一定优势，但 for 的特殊价值体现在于它能处理更复杂的迭代场景。让我们看看 range 和其他 Java 8 方法孰优孰劣。&lt;/p&gt; &lt;h2&gt;封闭范围&lt;/h2&gt; &lt;p&gt;创建 for 循环时，可以将索引变量封闭在一个范围内，比如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 6. 一个具有封闭范围的 for 循环:  for(int i = 0; i &amp;lt;= 5; i++) {} &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;索引变量 i 接受值 0、1、……5。无需使用 for，我们可以使用 rangeClosed 方法。在本例中，我们告诉 IntStream 将最后一个值限制在该范围内：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 7. rangeClosed 方法:  IntStream.rangeClosed(0, 5) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;迭代此范围时，我们会获得包含边界值 5 在内的值。&lt;/p&gt; &lt;h2&gt;跳过值&lt;/h2&gt; &lt;p&gt;对于基本循环，range 和 rangeClosed 方法是 for 的更简单、更优雅的替代方法，但是如果想跳过一些值该怎么办？在这种情况下，for 对前期工作的需求使该运算变得非常容易。在清单 8 中，for 循环在迭代期间快速跳过两个值：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 8. 使用 for 跳过值:  int total = 0; for(int i = 1; i &amp;lt;= 100; i = i + 3) {   total += i; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;清单 8 中的循环在 1 到 100 内对每次读到的第三个值作求和计算 — 这种复杂运算可使用 for 轻松完成。能否也使用 range 解决此问题？&lt;/p&gt; &lt;p&gt;首先，可以考虑使用 IntStream 的 range 方法，再结合使用 filter 或 map。但是，所涉及的工作比使用 for 循环要多。一种更可行的解决方案是结合使用 iterate 和 limit：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 9. 使用 limit 的迭代:  IntStream.iterate(1, e -&amp;gt; e + 3)   .limit(34)   .sum() &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;iterate 方法很容易使用；它只需获取一个初始值即可开始迭代。作为第二参数传入的Lambda表达式决定了迭代中的下一个值。这类似于清单 8，我们将一个表达式传递给 for 循环来递增索引变量的值。但是，在本例中有一个陷阱。不同于 range 和 rangeClosed，没有参数来告诉 iterate 方法何时停止迭代。如果我们没有限制该值，迭代会一直进行下去。&lt;/p&gt; &lt;p&gt;如何解决这个问题？&lt;/p&gt; &lt;p&gt;我们对 1 到 100 之间的值感兴趣，而且想从 1 开始跳过两个值。稍加运算，即可确定给定范围中有 34 个符合要求的值。所以我们将该数字传递给 limit 方法。&lt;/p&gt; &lt;p&gt;此代码很有效，但过程太复杂：提前执行数学运算不那么有趣，而且它限制了我们的代码。如果我们决定跳过 3 个值而不是 2 个值，该怎么办？我们不仅需要更改代码，结果也很容易出错。我们需要有一个更好的方法。&lt;/p&gt; &lt;h2&gt;takeWhile 方法&lt;/h2&gt; &lt;p&gt;Java 9 中即将引入的 takeWhile 是一个新方法，它使得执行有限制的迭代变得更容易。使用 takeWhile，可以直接表明只要满足想要的条件，迭代就应该继续执行。以下是使用 takeWhile 实现清单 9 中的迭代的代码。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 10. 有条件的迭代:  IntStream.iterate(1, e -&amp;gt; e + 3)      .takeWhile(i -&amp;gt; i &amp;lt;= 100) //available in Java 9      .sum() &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;无需将迭代限制到预先计算的次数，我们使用提供给 takeWhile 的条件，动态确定何时终止迭代。与尝试预先计算迭代次数相比，这种方法简单得多，而且更不容易出错。&lt;/p&gt; &lt;p&gt;与 takeWhile 方法相反的是 dropWhile，它跳过满足给定条件前的值，这两个方法都是 JDK 中非常需要的补充方法。takeWhile 方法类似于 break，而 dropWhile 则类似于 continue。从 Java 9 开始，它们将可用于任何类型的 Stream。&lt;/p&gt; &lt;h2&gt;逆向迭代&lt;/h2&gt; &lt;p&gt;与正向迭代相比，逆向迭代同样非常简单，无论使用传统的 for 循环还是 IntStream。&lt;/p&gt; &lt;p&gt;以下是一个逆向的 for 循环迭代：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 11. 使用 for 的逆向迭代:  for(int i = 7; i &amp;gt; 0; i--) { &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;range 或 rangeClosed 中的第一个参数不能大于第二个参数，所以我们无法使用这两种方法来执行逆向迭代。但可以使用 iterate 方法：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;清单 12. 使用 iterate 的逆向迭代:  IntStream.iterate(7, e -&amp;gt; e - 1)      .limit(7) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;将一个Lambda表达式作为参数传递给 iterate 方法，该方法对给定值进行递减，以便沿相反方向执行迭代。我们使用 limit 函数指定我们希望在逆向迭代期间看到总共多少个值。如有必要，还可以使用 takeWhile 和 dropWhile 方法来动态调整迭代流。&lt;/p&gt; &lt;h2&gt;结束语&lt;/h2&gt; &lt;p&gt;尽管传统 for 循环非常强大，但它有些过于复杂。Java 8 和 Java 9 中的新方法可帮助简化迭代，甚至是简化复杂的迭代。方法 range、iterate 和 limit 的可变部分较少，这有助于提高代码效率。这些方法还满足了 Java 的一个长期以来的要求，那就是局部变量必须声明为 final，然后才能从内部类访问它。将一个可变索引变量更换为实际的 final 参数只有很小的语义差别，但它减少了大量垃圾变量。最终您会得到更简单、更优雅的代码。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 28 Nov 2017 07:02:03 GMT</pubDate>
    </item>
    <item>
      <title>如何设计出高可用、高性能的接口</title>
      <link>https://maruifu.cn/article/59</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;接口设计怎么样才能高可用,高性能?&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;接口设计需要考虑哪些方面&lt;/h2&gt; &lt;ol&gt; &lt;li&gt;接口的命名。&lt;/li&gt; &lt;li&gt;请求参数。&lt;/li&gt; &lt;li&gt;支持的协议。&lt;/li&gt; &lt;li&gt;TPS、并发数、响应时长。&lt;/li&gt; &lt;li&gt;是否需要白名单。&lt;/li&gt; &lt;li&gt;数据存储。DB选型、缓存选型。&lt;/li&gt; &lt;li&gt;是否需要依赖于第三方。&lt;/li&gt; &lt;li&gt;接口是否拆分。&lt;/li&gt; &lt;li&gt;接口是否需要幂等。&lt;/li&gt; &lt;li&gt;防刷。&lt;/li&gt; &lt;li&gt;接口限流、降级。&lt;/li&gt; &lt;li&gt;负载均衡器支持。&lt;/li&gt; &lt;li&gt;如何部署。&lt;/li&gt; &lt;li&gt;是否需要服务治理。&lt;/li&gt; &lt;li&gt;是否存在单点。&lt;/li&gt; &lt;li&gt;接口是否资源包、预加载还是内置。&lt;/li&gt; &lt;li&gt;是否需要本地缓存。&lt;/li&gt; &lt;li&gt;是否需要分布式缓存、缓存穿透怎么办。&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;当我们设计接口，我们或多或少都会有上面列举的一些考虑，我们只有想的更多才能让让我们的接口更加完善，我个人觉得100%完美的接口是不存在，只有适合才是最重要。&lt;/p&gt; &lt;h2&gt;接口设计原则&lt;/h2&gt; &lt;p&gt;原则一：必须符合Restful，统一返回格式，约定业务层错误编码，每个编码可以携带可选的错误信息。&lt;/p&gt; &lt;p&gt;原则二： 命名必须规范、优雅。&lt;/p&gt; &lt;p&gt;原则三：单一性。&lt;/p&gt; &lt;p&gt;单一性是指接口要做的事情应该是一个比较单一的事情，比如登陆接口，登陆完成应该只是返回登陆成功以后一些用户信息即可，但很多人为了减少接口交互，返回一大堆额外的数据。比如有人设计一个用户列表接口，接口他返回每一条数据都是包含用户了一大堆跟另外无关的数据，结果一问，原来其他无关的数据是他下一步想要获取的，想达成数据的懒加载&lt;/p&gt; &lt;p&gt;原则四：可扩展。&lt;/p&gt; &lt;p&gt;接口扩展性，是指设计接口的时候多想想多种情况，多考虑各个方面，其实我觉得单独将扩展性放在这里也是不妥的，感觉说的跟单一性有点相反的意思，其实这个不是这个意思，这边的扩展性是指我们的接口充分考虑客户端，想想他们是如何调用的，他要怎样使用我的代码，他会如何扩展我的代码，不要把过多的工作写在你的接口里面，而应该把更多的主动权交给客户程序员。如获取不同的列表数据接口，我们不可能将每个列表都写成一个接口。 还有一点，我这里特别想指出来的是很多开发人员为了省事（姑且只能这么理解），将接口设计当成只是app页面展示，这些人将一个页面展示就用一个接口实现，而不考虑这些数据是不是属于不同的模块、是不是属于不同的展示范畴、结果下次视觉一改，整个接口又得重写，不能复用。&lt;/p&gt; &lt;p&gt;原则五：必须有文档。&lt;/p&gt; &lt;p&gt;良好的接口设计，离不开清晰的接口文档表述。文档表述一定要足够详细&lt;/p&gt; &lt;p&gt;原则六：产品心。&lt;/p&gt; &lt;p&gt;为什么我说要有产品心？因为我觉得很多人忽略了这一点。我来说一下假如开发一个app，如果一开始连个交互文档给你都没有的话，你怎么设计接口？所以我觉得作为一个服务端后台开发人员应该要有产品心，特别是对于交互文档应该好好理解，因为这些都会对我们的接口设计有很大的影响，我在设计接口的时候就很常发现很多交互文档根本就走不通，产品没有考虑到位，交互文档缺失，这时候作为一个开发要主动推动，完善。&lt;/p&gt; &lt;p&gt;原则七：第三方服务接口数据能缓存就缓存。&lt;/p&gt; &lt;p&gt;原则八：第三方服务需要做降级。&lt;/p&gt; &lt;p&gt;原则九：建议消除单点。&lt;/p&gt; &lt;p&gt;原则十：接口粒度要小。&lt;/p&gt; &lt;p&gt;原则十一：客户端能处理的逻辑就不要给服务端处理，减少服务端压力。&lt;/p&gt; &lt;p&gt;原则十二：资源预加载。&lt;/p&gt; &lt;p&gt;原则十三：不要过度设计。&lt;/p&gt; &lt;p&gt;原则十四：缓存尽量不要穿透。&lt;/p&gt; &lt;p&gt;原则十五：接口能缓存就缓存。&lt;/p&gt; &lt;p&gt;原则十六：思辨大于执行&lt;/p&gt; &lt;h2&gt;如何保证接口的高可用、高性能&lt;/h2&gt; &lt;p&gt;上面也列举很多需要考虑和设计的原则，其实还有很多方面，我这边也不是特别全面。居于上面列举的这些考虑点，其实这边说服务是更恰当，能把上面说的点做好，其实接口也是比较可靠，如何设计以及保证接口的高可用和高性能。可以思考一下以下几个point&lt;/p&gt; &lt;h3&gt;高性能：如果我们发现这个接口tps和响应时间没有达到我们的要求怎么办。&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;A：数据存储方面：我们会想数据库有没有分库、分表、有没有做主从，有没有读写分离、字段是否有加索引、是否存在慢sql，数据库引擎是否选用合适、是不是用了事务；其次我们会想到是不是引用了分布式缓存、缓存key大小是否合适，失效时间是否设置合理，会不会大量缓存穿透、有没有引入本地缓存。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;B：业务方面：是否有大量的计算、能否异步处理。是否需要引入线程池或者MQ来异步处理任务。有没有必要将接口进行垂直拆分和水平拆分、将接口粒度变小。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;C：其他方面：nginx层面做缓存、加机器、用ssd，资源放cdn，多机房部署、资源文件预加载。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;高可用：如何保证服务高可用，需要从几个维度来实现：&lt;/h3&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;A：消除单点，基于高可用第二位。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;B：能做集群的全部做集群。譬如Redis集群、mysql集群、MongoDB副本集。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;C：能做读写分离的都做读写分离。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;D：异地多机房部署，接入GSLB&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;E：必须有限流、降级机制。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;F：监控。高可用的保证，基于第一位&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;下图是从一个基本的请求出发来梳理需要涉及到各个段，以及各个端能做的事情。谈谈接口服务，但不局限于接口本身。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/jiekousheji.jpg" alt="接口设计" title="接口设计" /&gt;&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;客户端：资源预加载、限制请求、数据上报。我这边就拿客户端来举个例子。接口服务所依赖的资源包或者一些公共配置预加载在本地，减少接口的交互，通过请求配置文件是否更新，code是否是304等来；接口做一些请求限制，比如抢红包、抢券等，单位时间内N次点击只请求一次等；接口失败数据上报来；这就是客户端可以做到的对接口有帮助的事情&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;GSLB/HttpDNS：多机房部署、流量切换、域名劫持，一般技术和业务比较成熟的公司这一层。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;资源文件放CDN。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;负载均衡器：lVS+Nginx是互联网常用的做负载均衡，可以实现四层/七层负载均衡；这里除了可以分流、转发以外，我们用的更多的基于令牌桶限流、缓存。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;本地缓存。本地缓存能减少我们访问DB或者分布式缓存，本地缓存推荐使用guava，guava里面有很多特性很好用，例如基于令牌桶的限流；当缓存失效时只穿透一个请求去访问后端。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;线程池。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;模块拆分。将一个项目按功能模块拆分，一个接口也可以按业务粒度进行拆分。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据中心。提供数据支撑，譬如黑名单。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;数据库。加索引、分库、分表、读写分离&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;分布式缓存。数据分片、拆分大key，并做集群，采用分布式锁&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;MQ。做接口拆分利器，异步操作。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;其他服务。限流、防刷以及降级（特别是第三方服务，保证第三方服务down掉不要影响我们自身的服务）。在这里也需要考虑做第三方数据的缓存或者持久化，譬如实名认证、身份证认证等。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;监控。监控永远是必须的，能让你第一时间知道接口服务是否ok&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;h2&gt;个人小分享&lt;/h2&gt; &lt;p&gt;&lt;strong&gt;1）接口Restful，统一返回格式，约定业务层错误编码，每个编码可以携带可选的错误信息&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在前司，客户端和服务之间是有统一的数据返回格式，约定各层的编码，可以通过编码位数以及编码就可以看出是那一层出问题，我觉得这对我们定位问题以及维护来说具有莫大的意义，并对异常也进行捕捉，封装成对应的code，我之前阅读一些人的代码发现其项目根本没有做这一层，因为简单而不做我觉得有失所望。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2）采用hybird模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;采用hybird模式涉及到资源预加载的问题，在很多项目里面都大量使用，譬如前司的生活服务，就采用了hybird模式，先将资源文件（包含图片、前端页面）打包放到服务器并通过版本号进行管理，并通过一个总的配置文件来管理，如果是H5页面可以进行模板预先设计，down到本地。 配置文件格式：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   *文件1*         name：xxx         url：http:xxxx         md5：xxxx     *文件2*         name：zzz         url：http：zzzz         md5：zzz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;客户端每次启动应用或者定时请求总的配置文件，通过http code是否是304判断是否需要下载这个总的配置文件，如果code是200，那么下载这个配置，比较那个文件发生变化，并将其下载。这样的好处：&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;减少接口的交互；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;资源预加载，节省流量，打开页面更加流畅，对于服务端来说字需要返回数据json串就行，而不需要其他，减少服务端压力；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;方便开发人员，资源管理更加简洁，比如做活动需要的h5页面，只需要前端上传对应的h5资源包到服务端，不需要通过后端开发人员就可以搞定。&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;虽然这个原理很简单，但是现在很多app还是没有做这个，都是通过填写一个url，加载网页的方式去打开，体验性太不友好。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3）客户端&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;客户端跟服务端就是接口请求的关系，很多时候需要要求客户端做一些数据缓存的工作以及一些检验工作。在前司已经好几次给客户端的同学坑过了，客户端同学接口乱调用，死循环调用。一次是做一个关于事件提醒的功能，需要每天定时调用调用服务端一个接口，结果客户端的同学写了一个bug导致请求每隔一两秒就调用一次，导致服务器这边此接口pv翻了N倍，而且这个bug通过测试同学很难测试出来；还有一次发现服务端一段时间以后UV不见涨，但是PV却涨的很猛，定位发现是客户端同学A图省事在一个方法里面调用了N个接口，也就是模板方法，因为版本更新，同学B需要做一个新的功能，然后也调用了A同学的接口导致，从而导致PV上升，其实B同学完全不需要调用这么多接口。这些都是真实案例，所以这里需要有一个监控接口异常的机制。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4）思辨大于执行&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;写到这里觉得这个非常重要，思辨大于执行，意味着我们不是一股脑就去干，也不是不去干，我们做事情需要思考、辨别；从而让事情更高效、更好、更有力的执行。接口设计也一样，需要我们去思辨。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;5）本地缓存、分布式缓存以及异步&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;缓存在前司主要分为客户端缓存、CDN缓存、本地缓存（guava）、Redis缓存。在MZ早期是接口是采用DB+本地缓存的方式提供数据，但这种模式DB压力大，接口吞吐量小，本地缓存多机难一致性、更新不及时问题。为了解决这些问题，引入分布式缓存，并通过Task将业务数据刷到Redis，接口只访问redis，不会访问DB，及时DB故障也不会影响功能。不同的业务系统系统通过MQ来解耦，多机房不是通过MQ来实现数据的一直。比如，评论，先通过写Redis，写MQ来实现数据在多机房同步，再通过task将Redis中评论同步到DB中。&lt;/p&gt; &lt;p&gt;接口设计涉及方方面面，这边也只谈到一个大概，虽然有点泛泛而谈，希望此拙文对你有所启示。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;6）数据库&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;数据库分库分表，一般都是通过userId或者imei或者mac地址来分表，单表数据量控制在500w以内，这需要我们提前估算好数据量，尽量避免数据的迁移。在前司，数据库一般都是采用mysql+MongoDB两种，MySQL存储用户的用户数据，MongoDB存储业务数据，就像阅读和生活服务里面的业务数据就存储在MongoDB里面。在数据库这层，我们主要也是通过主从模式、读写分离、分库、分表来实现数据的可用性。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;7）业务&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;业务尽可能拆分、独立部署、将项目按业务划分、按功能划分等。譬如生活服务，我们当时主要拆分成管理后台admin、任务task、活动、web、数据展示模块。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8）数据中心&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;每个大一点的公司都有数据部门，我们这边可以通过数据中心的数据分析来达到我们需要的数据。 比如黑名单，推广效果、活动数据。我们可以通过这些完善我们的接口功能。之前在前司做了个数据处理后异步加载到Redis来实现数据利用的项目。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 28 Nov 2017 02:29:00 GMT</pubDate>
    </item>
    <item>
      <title>Linux 运维人员最常用 150 个命令汇总</title>
      <link>https://maruifu.cn/article/58</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;linux 命令是对 Linux 系统进行管理的命令。对于 Linux 系统来说，无论是中央处理器、内存、磁盘驱动器、键盘、鼠标，还是用户等都是文件， Linux 系统管理的命令是它正常运行的核心，与之前的 DOS 命令类似。linux 命令在系统中有两种类型：内置 Shell 命令和 Linux 命令。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="center"&gt;命令&lt;/th&gt;&lt;th&gt;功能说明&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="center"&gt;线上查询及帮助命令 (2 个)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;man&lt;/td&gt;&lt;td&gt;查看命令帮助，命令的词典，更复杂的还有   info，但不常用。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;help&lt;/td&gt;&lt;td&gt;查看   Linux 内置命令的帮助，比如 cd 命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;文件和目录操作命令 (18 个)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ls&lt;/td&gt;&lt;td&gt;全拼   list，功能是列出目录的内容及其内容属性信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;cd&lt;/td&gt;&lt;td&gt;全拼   change directory，功能是从当前工作目录切换到指定的工作目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;cp&lt;/td&gt;&lt;td&gt;全拼   copy，其功能为复制文件或目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;find&lt;/td&gt;&lt;td&gt;查找的意思，用于查找目录及目录下的文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mkdir&lt;/td&gt;&lt;td&gt;全拼   make directories，其功能是创建目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mv&lt;/td&gt;&lt;td&gt;全拼   move，其功能是移动或重命名文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;pwd&lt;/td&gt;&lt;td&gt;全拼   print working directory，其功能是显示当前工作目录的绝对路径。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;rename&lt;/td&gt;&lt;td&gt;用于重命名文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;rm&lt;/td&gt;&lt;td&gt;全拼   remove，其功能是删除一个或多个文件或目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;rmdir&lt;/td&gt;&lt;td&gt;全拼   remove empty directories，功能是删除空目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;touch&lt;/td&gt;&lt;td&gt;创建新的空文件，改变已有文件的时间戳属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tree&lt;/td&gt;&lt;td&gt;功能是以树形结构显示目录下的内容。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;basename&lt;/td&gt;&lt;td&gt;显示文件名或目录名。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dirname&lt;/td&gt;&lt;td&gt;显示文件或目录路径。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chattr&lt;/td&gt;&lt;td&gt;改变文件的扩展属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;lsattr&lt;/td&gt;&lt;td&gt;查看文件扩展属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;file&lt;/td&gt;&lt;td&gt;显示文件的类型。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;md5sum&lt;/td&gt;&lt;td&gt;计算和校验文件的   MD5 值。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;查看文件及内容处理命令（21 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;cat&lt;/td&gt;&lt;td&gt;全拼   concatenate，功能是用于连接多个文件并且打印到屏幕输出或重定向到指定文件中。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tac&lt;/td&gt;&lt;td&gt;tac   是 cat 的反向拼写，因此命令的功能为反向显示文件内容。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;more&lt;/td&gt;&lt;td&gt;分页显示文件内容。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;less&lt;/td&gt;&lt;td&gt;分页显示文件内容，more   命令的相反用法。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;head&lt;/td&gt;&lt;td&gt;显示文件内容的头部。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tail&lt;/td&gt;&lt;td&gt;显示文件内容的尾部。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;cut&lt;/td&gt;&lt;td&gt;将文件的每一行按指定分隔符分割并输出。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;split&lt;/td&gt;&lt;td&gt;分割文件为不同的小片段。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;paste&lt;/td&gt;&lt;td&gt;按行合并文件内容。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;sort&lt;/td&gt;&lt;td&gt;对文件的文本内容排序。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;uniq&lt;/td&gt;&lt;td&gt;去除重复行。oldboy&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;wc&lt;/td&gt;&lt;td&gt;统计文件的行数、单词数或字节数。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;iconv&lt;/td&gt;&lt;td&gt;转换文件的编码格式。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dos2unix&lt;/td&gt;&lt;td&gt;将   DOS 格式文件转换成 UNIX 格式。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;diff&lt;/td&gt;&lt;td&gt;全拼   difference，比较文件的差异，常用于文本文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;vimdiff&lt;/td&gt;&lt;td&gt;命令行可视化文件比较工具，常用于文本文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;rev&lt;/td&gt;&lt;td&gt;反向输出文件内容。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;grep/egrep&lt;/td&gt;&lt;td&gt;过滤字符串，三剑客老三。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;join&lt;/td&gt;&lt;td&gt;按两个文件的相同字段合并。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tr&lt;/td&gt;&lt;td&gt;替换或删除字符。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;vi/vim&lt;/td&gt;&lt;td&gt;命令行文本编辑器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;文件压缩及解压缩命令（4 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tar&lt;/td&gt;&lt;td&gt;打包压缩。oldboy&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;unzip&lt;/td&gt;&lt;td&gt;解压文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;gzip&lt;/td&gt;&lt;td&gt;gzip   压缩工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;zip&lt;/td&gt;&lt;td&gt;压缩工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;信息显示命令（11 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;uname&lt;/td&gt;&lt;td&gt;显示操作系统相关信息的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;hostname&lt;/td&gt;&lt;td&gt;显示或者设置当前系统的主机名。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dmesg&lt;/td&gt;&lt;td&gt;显示开机信息，用于诊断系统故障。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;uptime&lt;/td&gt;&lt;td&gt;显示系统运行时间及负载。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;stat&lt;/td&gt;&lt;td&gt;显示文件或文件系统的状态。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;du&lt;/td&gt;&lt;td&gt;计算磁盘空间使用情况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;df&lt;/td&gt;&lt;td&gt;报告文件系统磁盘空间的使用情况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;top&lt;/td&gt;&lt;td&gt;实时显示系统资源使用情况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;free&lt;/td&gt;&lt;td&gt;查看系统内存。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;date&lt;/td&gt;&lt;td&gt;显示与设置系统时间。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;cal&lt;/td&gt;&lt;td&gt;查看日历等时间信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;搜索文件命令（4 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;which&lt;/td&gt;&lt;td&gt;查找二进制命令，按环境变量   PATH 路径查找。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;find&lt;/td&gt;&lt;td&gt;从磁盘遍历查找文件或目录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;whereis&lt;/td&gt;&lt;td&gt;查找二进制命令，按环境变量   PATH 路径查找。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;locate&lt;/td&gt;&lt;td&gt;从数据库 (/var/lib/mlocate/mlocate.db) 查找命令，使用   updatedb 更新库。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;用户管理命令（10 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;useradd&lt;/td&gt;&lt;td&gt;添加用户。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;usermod&lt;/td&gt;&lt;td&gt;修改系统已经存在的用户属性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;userdel&lt;/td&gt;&lt;td&gt;删除用户。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;groupadd&lt;/td&gt;&lt;td&gt;添加用户组。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;passwd&lt;/td&gt;&lt;td&gt;修改用户密码。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chage&lt;/td&gt;&lt;td&gt;修改用户密码有效期限。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;id&lt;/td&gt;&lt;td&gt;查看用户的   uid,gid 及归属的用户组。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;su&lt;/td&gt;&lt;td&gt;切换用户身份。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;visudo&lt;/td&gt;&lt;td&gt;编辑   / etc/sudoers 文件的专属命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;sudo&lt;/td&gt;&lt;td&gt;以另外一个用户身份（默认   root 用户）执行事先在 sudoers 文件允许的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;基础网络操作命令（11 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;telnet&lt;/td&gt;&lt;td&gt;使用   TELNET 协议远程登录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ssh&lt;/td&gt;&lt;td&gt;使用   SSH 加密协议远程登录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;scp&lt;/td&gt;&lt;td&gt;全拼   secure copy，用于不同主机之间复制文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;wget&lt;/td&gt;&lt;td&gt;命令行下载文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ping&lt;/td&gt;&lt;td&gt;测试主机之间网络的连通性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;route&lt;/td&gt;&lt;td&gt;显示和设置   linux 系统的路由表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ifconfig&lt;/td&gt;&lt;td&gt;查看、配置、启用或禁用网络接口的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ifup&lt;/td&gt;&lt;td&gt;启动网卡。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ifdown&lt;/td&gt;&lt;td&gt;关闭网卡。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;netstat&lt;/td&gt;&lt;td&gt;查看网络状态。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ss&lt;/td&gt;&lt;td&gt;查看网络状态。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;深入网络操作命令（9 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;nmap&lt;/td&gt;&lt;td&gt;网络扫描命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;lsof&lt;/td&gt;&lt;td&gt;全名   list open files，也就是列举系统中已经被打开的文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mail&lt;/td&gt;&lt;td&gt;发送和接收邮件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mutt&lt;/td&gt;&lt;td&gt;邮件管理命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;nslookup&lt;/td&gt;&lt;td&gt;交互式查询互联网   DNS 服务器的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dig&lt;/td&gt;&lt;td&gt;查找   DNS 解析过程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;host&lt;/td&gt;&lt;td&gt;查询   DNS 的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;traceroute&lt;/td&gt;&lt;td&gt;追踪数据传输路由状况。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;tcpdump&lt;/td&gt;&lt;td&gt;命令行的抓包工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;有关磁盘与文件系统的命令（16 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mount&lt;/td&gt;&lt;td&gt;挂载文件系统。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;umount&lt;/td&gt;&lt;td&gt;卸载文件系统。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;fsck&lt;/td&gt;&lt;td&gt;检查并修复   Linux 文件系统。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dd&lt;/td&gt;&lt;td&gt;转换或复制文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dumpe2fs&lt;/td&gt;&lt;td&gt;导出   ext2/ext3/ext4 文件系统信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;dump&lt;/td&gt;&lt;td&gt;ext2/3/4   文件系统备份工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;fdisk&lt;/td&gt;&lt;td&gt;磁盘分区命令，适用于   2TB 以下磁盘分区。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;parted&lt;/td&gt;&lt;td&gt;磁盘分区命令，没有磁盘大小限制，常用于   2TB 以下磁盘分区。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mkfs&lt;/td&gt;&lt;td&gt;格式化创建   Linux 文件系统。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;partprobe&lt;/td&gt;&lt;td&gt;更新内核的硬盘分区表信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;e2fsck&lt;/td&gt;&lt;td&gt;检查   ext2/ext3/ext4 类型文件系统。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mkswap&lt;/td&gt;&lt;td&gt;创建   Linux 交换分区。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;swapon&lt;/td&gt;&lt;td&gt;启用交换分区。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;swapoff&lt;/td&gt;&lt;td&gt;关闭交换分区。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;sync&lt;/td&gt;&lt;td&gt;将内存缓冲区内的数据写入磁盘。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;resize2fs&lt;/td&gt;&lt;td&gt;调整   ext2/ext3/ext4 文件系统大小。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;系统权限及用户授权相关命令（4 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chmod&lt;/td&gt;&lt;td&gt;改变文件或目录权限。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chown&lt;/td&gt;&lt;td&gt;改变文件或目录的属主和属组。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chgrp&lt;/td&gt;&lt;td&gt;更改文件用户组。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;umask&lt;/td&gt;&lt;td&gt;显示或设置权限掩码。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;查看系统用户登陆信息的命令（7 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;whoami&lt;/td&gt;&lt;td&gt;显示当前有效的用户名称，相当于执行   id -un 命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;who&lt;/td&gt;&lt;td&gt;显示目前登录系统的用户信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;w&lt;/td&gt;&lt;td&gt;显示已经登陆系统的用户列表，并显示用户正在执行的指令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;last&lt;/td&gt;&lt;td&gt;显示登入系统的用户。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;lastlog&lt;/td&gt;&lt;td&gt;显示系统中所有用户最近一次登录信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;users&lt;/td&gt;&lt;td&gt;显示当前登录系统的所有用户的用户列表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;finger&lt;/td&gt;&lt;td&gt;查找并显示用户信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;内置命令及其它（19 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;echo&lt;/td&gt;&lt;td&gt;打印变量，或直接输出指定的字符串&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;printf&lt;/td&gt;&lt;td&gt;将结果格式化输出到标准输出。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;rpm&lt;/td&gt;&lt;td&gt;管理   rpm 包的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;yum&lt;/td&gt;&lt;td&gt;自动化简单化地管理   rpm 包的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;watch&lt;/td&gt;&lt;td&gt;周期性的执行给定的命令，并将命令的输出以全屏方式显示。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;alias&lt;/td&gt;&lt;td&gt;设置系统别名。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;unalias&lt;/td&gt;&lt;td&gt;取消系统别名。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;date&lt;/td&gt;&lt;td&gt;查看或设置系统时间。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;clear&lt;/td&gt;&lt;td&gt;清除屏幕，简称清屏。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;history&lt;/td&gt;&lt;td&gt;查看命令执行的历史纪录。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;eject&lt;/td&gt;&lt;td&gt;弹出光驱。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;time&lt;/td&gt;&lt;td&gt;计算命令执行时间。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;nc&lt;/td&gt;&lt;td&gt;功能强大的网络工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;xargs&lt;/td&gt;&lt;td&gt;将标准输入转换成命令行参数。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;exec&lt;/td&gt;&lt;td&gt;调用并执行指令的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;export&lt;/td&gt;&lt;td&gt;设置或者显示环境变量。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;unset&lt;/td&gt;&lt;td&gt;删除变量或函数。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;type&lt;/td&gt;&lt;td&gt;用于判断另外一个命令是否是内置命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;bc&lt;/td&gt;&lt;td&gt;命令行科学计算器&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;系统管理与性能监视命令 (9 个)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;chkconfig&lt;/td&gt;&lt;td&gt;管理   Linux 系统开机启动项。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;vmstat&lt;/td&gt;&lt;td&gt;虚拟内存统计。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;mpstat&lt;/td&gt;&lt;td&gt;显示各个可用   CPU 的状态统计。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;iostat&lt;/td&gt;&lt;td&gt;统计系统   IO。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;sar&lt;/td&gt;&lt;td&gt;全面地获取系统的   CPU、运行队列、磁盘 I/O、分页（交换区）、内存、 CPU 中断和网络等性能数据。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ipcs&lt;/td&gt;&lt;td&gt;用于报告   Linux 中进程间通信设施的状态，显示的信息包括消息列表、共享内存和信号量的信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ipcrm&lt;/td&gt;&lt;td&gt;用来删除一个或更多的消息队列、信号量集或者共享内存标识。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;strace&lt;/td&gt;&lt;td&gt;用于诊断、调试   Linux 用户空间跟踪器。我们用它来监控用户空间进程和内核的交互，比如系统调用、信号传递、进程状态变更等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ltrace&lt;/td&gt;&lt;td&gt;命令会跟踪进程的库函数调用,   它会显现出哪个库函数被调用。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;关机 / 重启 / 注销和查看系统信息的命令（6 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;shutdown&lt;/td&gt;&lt;td&gt;关机。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;halt&lt;/td&gt;&lt;td&gt;关机。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;poweroff&lt;/td&gt;&lt;td&gt;关闭电源。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;logout&lt;/td&gt;&lt;td&gt;退出当前登录的   Shell。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;exit&lt;/td&gt;&lt;td&gt;退出当前登录的   Shell。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;Ctrl+d&lt;/td&gt;&lt;td&gt;退出当前登录的   Shell 的快捷键。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;进程管理相关命令（15 个）&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;bg&lt;/td&gt;&lt;td&gt;将一个在后台暂停的命令，变成继续执行  （在后台执行）。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;fg&lt;/td&gt;&lt;td&gt;将后台中的命令调至前台继续运行。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;jobs&lt;/td&gt;&lt;td&gt;查看当前有多少在后台运行的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;kill&lt;/td&gt;&lt;td&gt;终止进程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;killall&lt;/td&gt;&lt;td&gt;通过进程名终止进程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;pkill&lt;/td&gt;&lt;td&gt;通过进程名终止进程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;crontab&lt;/td&gt;&lt;td&gt;定时任务命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;ps&lt;/td&gt;&lt;td&gt;显示进程的快照。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;pstree&lt;/td&gt;&lt;td&gt;树形显示进程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;nice/renice&lt;/td&gt;&lt;td&gt;调整程序运行的优先级。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;nohup&lt;/td&gt;&lt;td&gt;忽略挂起信号运行指定的命令。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;pgrep&lt;/td&gt;&lt;td&gt;查找匹配条件的进程。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;runlevel&lt;/td&gt;&lt;td&gt;查看系统当前运行级别。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;init&lt;/td&gt;&lt;td&gt;切换运行级别。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;service&lt;/td&gt;&lt;td&gt;启动、停止、重新启动和关闭系统服务，还可以显示所有系统服务的当前状态。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Thu, 23 Nov 2017 01:15:07 GMT</pubDate>
    </item>
    <item>
      <title>wget下载JDK</title>
      <link>https://maruifu.cn/article/57</link>
      <content:encoded>&lt;p&gt;大家都知道jdk无法直接通过wget下载。原来需要cookie，如下：&lt;/p&gt; &lt;p&gt;一、下载&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget --no-check-certificate --no-cookie --header &amp;quot;Cookie: oraclelicense=accept-securebackup-cookie;&amp;quot; http://download.oracle.com/otn/java/jdk/7u80-b15/jdk-7u80-linux-x64.rpm sudo rpm -ivh jdk-7u79-linux-x64.rpm &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在下载的要求又有所变化，用原来的方式已经不行了。是动态的生成一个参数。&lt;/p&gt; &lt;p&gt;解决办法： 在chrome下，打开开发者工具（本人用的mac版本），点击所需要下载的包，看console-&amp;gt;logs会看到一行字：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Resource interpreted as Document but transferred with MIME type application/x-redhat-package-manager: &amp;quot;http://download.oracle.com/otn/java/jdk/7u80-b15/jdk-7u80-linux-x64.rpm?AuthParam=1461049990_341c3c217ccd4554c0a065149ff156c8&amp;quot;. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;，于是，直接使用这个就好了。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget -O jdk-7u80-linux-x64.rpm http://download.oracle.com/otn/java/jdk/7u80-b15/jdk-7u80-linux-x64.rpm?AuthParam=1461049990_341c3c217ccd4554c0a065149ff156c8   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;64位&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget --no-cookies --no-check-certificate --header &amp;quot;Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie&amp;quot; &amp;quot;http://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb4ef291e347e091f7f4a7/jdk-8u141-linux-x64.tar.gz&amp;quot; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;32位&lt;/p&gt; &lt;pre&gt;&lt;code&gt;wget --no-cookies --no-check-certificate --header &amp;quot;Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie&amp;quot; &amp;quot;http://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb4ef291e347e091f7f4a7/jdk-8u141-linux-i586.tar.gz&amp;quot; tar xzf jdk-8u141-linux-i586.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解压&lt;/p&gt; &lt;pre&gt;&lt;code&gt;tar xzf jdk-8u141-linux-x64.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;配置环境变量&lt;/p&gt; &lt;pre&gt;&lt;code&gt;JAVA_HOME=/usr/local/java/jdk1.8/ JRE_HOME=/usr/local/java/jdk1.8/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;为了让环境变量即时生效，输入  &lt;code&gt;source /etc/profile&lt;/code&gt; 即可。&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 16 Nov 2017 11:25:17 GMT</pubDate>
    </item>
    <item>
      <title>阿里云ECS CentOS7.4 64位系统安装nginx</title>
      <link>https://maruifu.cn/article/56</link>
      <content:encoded>&lt;blockquote&gt; &lt;p&gt;本次安装目录在 /usr/local/server 如需修改请将后文出现的此地址修改为自定义位置&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;官网下载Nginx&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;去官网下载需要的nginx压缩包，地址：http://nginx.org/en/download.html，此处下载最新稳定版nginx-1.22.1。&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2023/04/06/image-20230406085813849.png" alt="image-20230406085813849" title="image-20230406085813849" /&gt;&lt;/p&gt; &lt;p&gt;下载安装包&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 创建目录 mkdir /usr/local/server # 前往安装目录 cd /usr/local/server # 下载安装包 wget http://nginx.org/download/nginx-1.22.1.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;非wget 直接下载，单独下载请使用rz 上传安装包&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 需要从服务器上传递、下载文件，还可以安装上文件传输依赖 yum install lrzsz -y # rz 上传文件 rz # sz + 文件名 下载文件 sz nginx-1.22.1.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;解压安装包&lt;/p&gt; &lt;pre&gt;&lt;code&gt;tar -xvf nginx-1.22.1.tar.gz &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装依赖包&lt;/h2&gt; &lt;pre&gt;&lt;code class="language-shell"&gt; yum install gcc-c++yum install -y pcre pcre-develyum install -y zlib zlib-develyum install -y openssl openssl-devel &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;配置Nginx&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 前往编译目录 cd /usr/local/server/nginx-1.22.1 # 配置nginx ./configure --prefix=/usr/local/server/nginx --with-http_stub_status_module --with-http_ssl_module &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;安装Nginx&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 执行编译命令 make # 执行安装命令 make install &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;启动Nginx&lt;/h2&gt; &lt;pre&gt;&lt;code&gt;# 前往启动目录 /usr/local/server/nginx/sbin # 启动nginx执行命令 ./nginx &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;其他nginx命令&lt;/h3&gt; &lt;blockquote&gt; &lt;p&gt;在nginx/sbin目录下执行&lt;/p&gt; &lt;/blockquote&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;# 强制停止nginx命令 ./nginx -s stop # 优雅停止命令，等最后一次交互执行完毕再停止 ./nginx -s quit # 重启命令 ./nginx -s reload # 检查配置文件是否有问题 ./nginx -t # 查看nginx版本信息 ./nginx -v # 查看nginx详细版本信息 ./nginx -V &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;防火墙放开端口&lt;/h2&gt; &lt;p&gt;查看防火墙是否开启&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;systemctl status firewalld &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当防火墙为关闭时，执行开启防火墙命令：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;systemctl start firewalld &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;把要对外开放的端口号添加到防火墙，例如放开nginx默认的80端口：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;firewall-cmd --permanent --zone=public --add-port=80/tcp &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;需要重启防火墙，加入的端口才能生效，执行重启命令：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;systemctl reload firewalld &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输入命令查看当前防火墙放开的端口号集合：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-shell"&gt;firewall-cmd --list-ports &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;https配置&lt;/h2&gt; &lt;blockquote&gt; &lt;p&gt;nginx 的https 功能基于模块ngx_http_ssl_module实现，因此如果是编译安装的nginx要使用参数 ngx_http_ssl_module开启ssl功能，但是作为nginx的核心功能，yum安装的nginx默认就是开启的，编译安装的nginx需要指定编译参数–with-http_ssl_module开启&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;配置参数&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    # 为指定的虚拟主机配置启用ssl功能     listen 443 ssl;     # 将这里的域名改成自己的域名     server_name maruifu.com;      #指向包含当前主机和CA的两个证书信息的文件，crt或pem   一般是crt文件     ssl_certificate /usr/local/nginx/conf/maruifu.com.crt;     #当前主机使用的私钥文件，一般是key文件     ssl_certificate_key /usr/local/nginx/conf/maruifu.com.key;          #客户端连接可以复用ssl session cache中缓存的有效时长，默认5分钟     ssl_session_timeout 5m;     # 支持ssl协议版本，早期为ssl现在是TLS，默认为后三个,最新的浏览器已经不再支持TLS1.0和TLS1.1     ssl_protocols TLSv1 TLSv1.1 TLSv1.2;     # 用于指定所需的SSL/TLS密码组，即用于加密客户端和服务器之间的通信     ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;     # 它用于指定是否优先使用服务器端选择的密码组，或是客户端选择的密码组。     ssl_prefer_server_ciphers on;          # 配置ssl缓存 off | none | [builtin[:size]] [shared:name:size];     # off 关闭缓存     # none 通知客户端支持ssl session cache，但实际不支持     # builtin[:size] 使用OpenSSL内建缓存，为每worker进程私有,使用此内置缓存可能会导致内存碎片     # [shared:name:size]：#在各worker之间使用一个共享的缓存，需要定义一个缓存名称和缓存空间大小，1M可以存储4000个会话信息，多个虚拟主机可以使用相同的缓存名称     ssl_session_cache shared:sslcache:20m;               location / {         # 页面存放到这里 /usr/share/nginx/html/maruifu.com         root /usr/share/nginx/html/maruifu.com;     } &lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt; &lt;p&gt;官方性能优化 可参照https://nginx.org/en/docs/http/ngx_http_ssl_module.html&lt;/p&gt; &lt;/blockquote&gt; &lt;h2&gt;常见使用模块&lt;/h2&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;&lt;strong&gt;序号&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;模块名&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;作用&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_core_module：&lt;/td&gt;&lt;td&gt;核心模块，提供HTTP协议能力，包括请求解析、请求路由、响应处理等功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_ssl_module：&lt;/td&gt;&lt;td&gt;提供SSL/TLS协议支持，可以实现HTTP安全连接。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_v2_module：&lt;/td&gt;&lt;td&gt;提供HTTP/2协议支持，可以加速连接速度，降低延迟。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_rewrite_module：&lt;/td&gt;&lt;td&gt;提供URL重写功能，可以将URL进行重写，增强URL透明性和可读性。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_module：&lt;/td&gt;&lt;td&gt;提反向代理功能，可以将请求转发到后端服务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_fastcgi_module：&lt;/td&gt;&lt;td&gt;提供FastCGI协议支持，可以将请求转发到FastCGI后端服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_limit_conn_module：&lt;/td&gt;&lt;td&gt;提供连接限速功能，可以限制对应客户端的连接速度。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_gzip_module：&lt;/td&gt;&lt;td&gt;提供Gzip压缩功能，可以压缩HTTP响应数据，减少网络带宽。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_stub_status_module：&lt;/td&gt;&lt;td&gt;提供基础统计信息，可以查看当前Nginx服务器的基础状态数据。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_access_module：&lt;/td&gt;&lt;td&gt;提供访问控制功能，可以限制对应客户端的访问权限。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_auth_basic_module：&lt;/td&gt;&lt;td&gt;提供HTTP基础认证功能，可以对访问进行安全认证。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_real_module：&lt;/td&gt;&lt;td&gt;实现正则表达式以及URL匹配功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;13&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_upstream_module：&lt;/td&gt;&lt;td&gt;提供后端服务均衡功能，可以将请求负载分发到多个后端服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_sub_module：&lt;/td&gt;&lt;td&gt;提供文本替换功能，可以将响应内容进行替换。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_autoindex_module：&lt;/td&gt;&lt;td&gt;提供目录列表功能，可以展示目录列表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_secure_link_module：&lt;/td&gt;&lt;td&gt;提供生成链接的安全加密功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_headers_module：&lt;/td&gt;&lt;td&gt;提供HTTP头处理功能，可以设置HTTP响应头部。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;18&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_addition_module：&lt;/td&gt;&lt;td&gt;提供HTTP响应数据拼接功能，可以将另一个接给客户端。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;19&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_map_module：&lt;/td&gt;&lt;td&gt;提供配置文件的映射功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_geo_module：&lt;/td&gt;&lt;td&gt;提供根据客户端IP地址进行访问控制的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_empty_gif_module：&lt;/td&gt;&lt;td&gt;提供占位图的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;22&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_slice_module：&lt;/td&gt;&lt;td&gt;提供文件下载功能，支持断点续传。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;23&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_random_index_module：&lt;/td&gt;&lt;td&gt;提供随机访问首页的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;24&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_degradation_module：&lt;/td&gt;&lt;td&gt;提供降级功能，可以根据配置规则，对请求进行降级处理。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_browser_module：&lt;/td&gt;&lt;td&gt;提供浏览器兼容性检查功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;26&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_userid_module：&lt;/td&gt;&lt;td&gt;提供用户唯一标识功能，可以在会话中识别&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;27&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_httper_module：&lt;/td&gt;&lt;td&gt;提供HTTP引用来源检查功能，可以进行反垃圾邮件检查。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;28&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http提供代理检查功能，可以根据用户代理识别浏览器类型和版本。&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;29&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_image_filter_module：&lt;/td&gt;&lt;td&gt;提供图片处理功能，可以对图片进行大小和格式的转换。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_dav_module：&lt;/td&gt;&lt;td&gt;提供WebDAV功能，支持对功能的删除、添加或修改等操作。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;31&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_mp4_module：&lt;/td&gt;&lt;td&gt;提供流式媒体视频服务，支持MP4格式。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;32&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_memcached_module：&lt;/td&gt;&lt;td&gt;提供memcached缓存功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;33&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_scgi_module：&lt;/td&gt;&lt;td&gt;提供SC，可以将请求转发到SCGI后端服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;34&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_headers_more_module：&lt;/td&gt;&lt;td&gt;提供HTTP头部扩展功能，可以增强HTTP头的处理能力。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_flv_live_module：&lt;/td&gt;&lt;td&gt;提供流媒体服务，支持FLV格式。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_echo_module：&lt;/td&gt;&lt;td&gt;提供输出数据到HTTP响应体的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;37&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_footer_filter_module：&lt;/td&gt;&lt;td&gt;提供在响应尾部输出HTML代码的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;38&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_addition_filter_module：&lt;/td&gt;&lt;td&gt;与ngx_http_addition_module配合使用，增强其功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;39&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_core_module：&lt;/td&gt;&lt;td&gt;提供流式数据处理能力，可以进行TCP/UDP协议的支持。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_proxy_module：&lt;/td&gt;&lt;td&gt;提供TCP反向代理功能，可以将TCP请求转发到后端服务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;41&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_ssl_module：&lt;/td&gt;&lt;td&gt;提供TCP加密功能，可以对TCP数据进行安全传输。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;42&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_realip_module：&lt;/td&gt;&lt;td&gt;提供反向代理转发时进行真实IP地址重写。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;43&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_limit_conn_module：&lt;/td&gt;&lt;td&gt;该模块可以限制每个客户端的连接数量&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;44&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_access_module：&lt;/td&gt;&lt;td&gt;提供访问控制功能，可以限制对应客户端的访问权限。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;45&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_geo_module：&lt;/td&gt;&lt;td&gt;提供根据客户端IP地址进行访问控制的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;46&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_upstream_module：&lt;/td&gt;&lt;td&gt;提供后端服务的负载均衡功能，可以将请求负载分发到多个后端服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;47&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_map_module：&lt;/td&gt;&lt;td&gt;提供配置文件的映射功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;48&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_empty_gif_module：&lt;/td&gt;&lt;td&gt;提供占位图的功能。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;49&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_stream_split_clients_module：&lt;/td&gt;&lt;td&gt;支持模块加载和配置，用于实现服务分流。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ngx_http_js_module：&lt;/td&gt;&lt;td&gt;提供JS执行能力，可以将JavaScript代码嵌入到Nginx中执行&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Thu, 16 Nov 2017 06:38:00 GMT</pubDate>
    </item>
    <item>
      <title>怎么样导入RobotFramework 自定义关键字（库文件）</title>
      <link>https://maruifu.cn/article/55</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;标准库关键字在使用的过程中，简单的需求还是可以满足。但是遇到有些需求还是不好满足的，还是需要自己去编码，自定义库文件关键字。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;假设python的安装路径在D:/下面&lt;/p&gt; &lt;p&gt;第一步：在D:/python/Lib/site-packages 建立库文件文件夹 MyLibrary&lt;/p&gt; &lt;p&gt;第二步：在MyLibrary 内建立 mytool.py 文件&lt;/p&gt; &lt;p&gt;这个文件里面写自己需要创建的关键字既方法，如下代码里面有一个比较两参数大小的方法，在Robot Framework 里面将以关键字的方式使用&lt;/p&gt; &lt;pre&gt;&lt;code&gt; # coding=utf-8  class mytool():      def __init__(self):          pass      def test_a_b(self,a,b):          '''          比较两个参数的大小          '''          if a&amp;gt;b:               flag = False               return flag           else:               flag = True              return flag &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第三步：在MyLibrary 内建立 init.py 文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt; #如下文件内容模板，注意类名要与库文件夹名称一致  # coding=utf-8  from mytool import mytool  version = '1.0'  class MyLibrary(mytool):     ROBOT_LIBRARY_SCOPE = 'GLOBAL' &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;ROBOT_LIBRARY_SCOPE = ‘GLOBAL’这一句代表库是全局的&lt;/p&gt; &lt;p&gt;version='1.0'版本定义&lt;/p&gt; &lt;p&gt;第四步：RIDE中导入库&lt;/p&gt; &lt;p&gt;RobotFramework 库文件导入&lt;/p&gt; &lt;p&gt;第五步：自定义使用&lt;/p&gt; &lt;p&gt;导入完成之后，我们可以在Ride中键盘按F5到Search Keywords , source选择我们刚刚导入的库，我们会在面板中看见我们库中所有的方法的介绍&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/18/2021-05-18-10.03.02.png" alt="10.03.02" title="10.03.02" /&gt;&lt;/p&gt; &lt;p&gt;使用关键字&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/2021/05/18/2021-05-18-10.04.30.png" alt="截屏2021-05-18 下午10.04.30" title="截屏2021-05-18 下午10.04.30" /&gt; 运行结果: &lt;img src="https://img.maruifu.com/images/2021/05/18/2021-05-18-10.05.05.png" alt="截屏2021-05-18 下午10.04.30" title="截屏2021-05-18 下午10.04.30" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Mon, 13 Nov 2017 12:35:00 GMT</pubDate>
    </item>
    <item>
      <title>Eclipse 卡慢的完美解决方案</title>
      <link>https://maruifu.cn/article/54</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;eclipse 有时候保存一下就不懂了,卡主了,Clena 一下特别慢&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;一、产生这个问题的原因多种&lt;/p&gt; &lt;p&gt;1、自动升级 2、未正确关闭  3、maven下载lib挂起 等..&lt;/p&gt; &lt;p&gt;二、解决总结&lt;/p&gt; &lt;p&gt;（1）、解决方法&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    方法1.修改eclipse启动文件 eclipse.ini 中添加启动参数参数: -vmargs -Xmx512m     方法2.关闭自动构建工作区: project -&amp;gt; build Auto…..　     方法3.在eclipse.ini式中添加了一个命令 -clean  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（2）、加大Eclipse运行可用最大内存数&lt;/p&gt; &lt;pre&gt;&lt;code&gt;      具体操作: 修改位于eclipse目录下的eclipse.ini, 将-Xmx512m调高, 如改成-Xmx768m &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（3）、减少Eclipse启动后自动启动的插件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;      具体操作: 在Preferences -&amp;gt; General -&amp;gt; StartUp and Shutdown:         Eclipse加载慢，有时候是因为插件装多了，可以转到window → Preperences → 搜索Startup and Shutdown → 取消激活所有的插件（Plug-ins activated on startup 程序启动时激活插件）将除Plug-ins activated on startup以外的项目有节选的去掉（比如Mylyn等没用到,就去掉了）        效果: 启动Eclipse后,会有Initialing Java Tools的滚动条,会发现快了很多. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（4）、减少编译需要验证的项目,提升编译速度&lt;/p&gt; &lt;pre&gt;&lt;code&gt;      具体操作: 在Preferences -&amp;gt; Validataion 将无关的Validator去掉, 比如: 我就将和我无关的JPA, JSP, WS 都去掉了.        效果: 编译项目时,Eclipse跑的Validator项目少了, 确实快了. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（5）、关掉自动编译&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    具体操作: Project -&amp;gt; Build Automatically      效果: 在代码修改保存后,不会启动自动编译. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（6）、在Clean的时候,要注意选项&lt;/p&gt; &lt;pre&gt;&lt;code&gt;      具体操作: Project -&amp;gt; Clean        注意: 在最下面有: Build the entire workspace 和 Build Only the selected Projects         要根据自己情况勾选, 因为是默认选择编译整个工作区. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（7）、显示内存使用情况（可手动GC）&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 具体操作：Preference -&amp;gt; General -&amp;gt; Show heep status &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（8）、保存自己的Perspective&lt;/p&gt; &lt;pre&gt;&lt;code&gt; 具体操作：1. Window -&amp;gt; Save Perspective As    2. Preference -&amp;gt; Perspective -&amp;gt; Make Default 将自己刚刚创建的Perspective 或 自己常用的 设置成默认 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;（9）、关闭Server的自动发布&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   具体操作：Server -&amp;gt; Publishing -&amp;gt; Never publish automatically &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Mon, 13 Nov 2017 02:56:25 GMT</pubDate>
    </item>
    <item>
      <title>SpringBoot系列</title>
      <link>https://maruifu.cn/article/53</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Spring Boot是由Pivotal团队提供的全新框架，其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置，从而使开发人员不再需要定义样板化的配置。通过这种方式，Boot致力于在蓬勃发展的快速应用开发领域（rapid application development）成为领导者。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;SpringBoot系列-1入门&lt;/h2&gt; &lt;h3&gt;相关介绍&lt;/h3&gt; &lt;p&gt;官网:&lt;a href="https://projects.spring.io/spring-boot" title="https://projects.spring.io/spring-boot" target="_blank"&gt;https://projects.spring.io/spring-boot&lt;/a&gt;&lt;/p&gt; &lt;p&gt;官方应用： SpringBoot-&amp;gt;SpringCloud&amp;gt;微服务&lt;/p&gt; &lt;p&gt;Spring Boot不是一门新技术。从本质上来说，Spring Boot就是Spring,它做了那些没有它你也会去做的Spring Bean配置。它使用“习惯优于配置”（项目中存在大量的配置，此外还内置了一个习惯性的配置，让你无需手动进行配置）的理念让你的项目快速运行起来。使用Spring Boot很容易创建一个独立运行（运行jar,内嵌Servlet容器）、准生产级别的基于Spring框架的项目，使用Spring Boot基本上可以不用或者只需要很少的Spring配置&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/SpringBoot1.png" alt="" title="" /&gt;&lt;/p&gt; &lt;h3&gt;SpringBoot精要&lt;/h3&gt; &lt;p&gt;SpringBoot是伴随着Spring4.0诞生的&lt;/p&gt; &lt;p&gt;SpringBoot帮助开发者快速启动一个Web容器&lt;/p&gt; &lt;p&gt;SpringBoot继承了原有Spring框架的优秀基因&lt;/p&gt; &lt;p&gt;SpringBoot简化了使用Spring的过程 &lt;img src="https://img.maruifu.com/images/blog/blogimg/SpringBoot2.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;自动配置：针对很多Spring应用程序常见的应用功能，Spring Boot能自动提供相关配置&lt;/p&gt; &lt;p&gt;起步依赖：告诉Spring Boot需要什么功能，它就能引入需要的库。&lt;/p&gt; &lt;p&gt;命令行界面：这是Spring Boot的可选特性，借此你只需写代码就能完成完整的应用程序，无需传统项目构建。&lt;/p&gt; &lt;p&gt;Actuator：让你能够深入运行中的Spring Boot应用程序，探测各种指标。&lt;/p&gt; &lt;h3&gt;springboot使开发更简单&lt;/h3&gt; &lt;h4&gt;springboot使配置更简单&lt;/h4&gt; &lt;p&gt;功能的组合配置:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;从XML config -&amp;gt; Java config Bean  注入   -&amp;gt;  Autowire &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;部署环境配置:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;从多个 *peoperties  -&amp;gt; 单个 Application.yml(或者properties) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;springboot使监控更简单:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;spring-boot-actuator:  /configpros            查看属性配置 /dump                    线程工作状态 /env/{name}           环境变量 /metrics/{name}     JVM性能指标 /mappings       RestFul Path 与服务类的映射关系 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;springboot使服务化开发更简单:&lt;/p&gt; &lt;p&gt;多个client &amp;gt;&amp;gt;&amp;gt;  通过Api接口 &amp;lt;&amp;lt;&amp;lt; Rest API&amp;gt;&amp;gt;&amp;gt; 多个Springboot SPI&lt;/p&gt; &lt;h3&gt;系统要求&lt;/h3&gt; &lt;p&gt;目前Spring Boot最新正式版为1.5.8.RELEASE&lt;/p&gt; &lt;p&gt;默认情况下，Spring Boot 1.5.X.RELEASE需要Java 7和Spring Framework 4.3以上或更高版本,我们可以使用Spring Boot with Java 6和一些额外的配置（不建议）,使用Maven（3.2+）或Gradle 2（2.9或更高版本）和3来构建。&lt;/p&gt; &lt;p&gt;大家可以使用Java 6或7的Spring Boot，通常推荐Java 8。 本次统一使用Java 1.8，Spring Boot 1.5.1.RELEASE以及Maven3.3.9版本。开发工具使用STS&lt;/p&gt; &lt;h3&gt;快速入门&lt;/h3&gt; &lt;p&gt;传统Spring MVC开发一个简单的Hello World Web应用程序，你应该做什么？&lt;/p&gt; &lt;p&gt;一个项目结构，其中有一个包含必要依赖的Maven或者Gradle构建文件，最起码要有Spring MVC和Servlet API这些依赖。&lt;/p&gt; &lt;p&gt;一个web.xml文件（或者一个WebApplicationInitializer实现），其中声明了Spring的DispatcherServlet。&lt;/p&gt; &lt;p&gt;一个启动了Spring MVC的Spring配置&lt;/p&gt; &lt;p&gt;一控制器类，以“hello World”相应HTTP请求。&lt;/p&gt; &lt;p&gt;一个用于部署应用程序的Web应用服务器，比如Tomcat。&lt;/p&gt; &lt;p&gt;最让人难以接受的是，这份清单里面只有一个东西是和Hello World功能相关的，即控制器，剩下的都是Spring开发的Web应用程序必需的通用模板。&lt;/p&gt; &lt;p&gt;接下来看看Spring Boot如何搞定？ 很简单，我仅仅只需要非常少的几个配置就可以迅速方便的搭建起来一套web项目&lt;/p&gt; &lt;h2&gt;SpringBoot系列-2配置&lt;/h2&gt; &lt;p&gt;Spring Boot使用了一个全局的配置文件application.properties，放在src/main/resources目录下或者类路径的/config下。Sping Boot的全局配置文件的作用是对一些默认配置的配置值进行修改。&lt;/p&gt; &lt;p&gt;1、自定义属性&lt;/p&gt; &lt;p&gt;2、参数引用&lt;/p&gt; &lt;p&gt;3、引用自定义配置文件&lt;/p&gt; &lt;p&gt;4、配置随机值&lt;/p&gt; &lt;p&gt;5、内置属性配置&lt;/p&gt; &lt;h3&gt;配置文件加载优先级&lt;/h3&gt; &lt;p&gt;application.properties和application.yml文件可以放在一下四个位置：&lt;/p&gt; &lt;p&gt;外置：在相对于应用程序运行目录的/congfig子目录里。&lt;/p&gt; &lt;p&gt;外置：在应用程序运行的目录里&lt;/p&gt; &lt;p&gt;内置：在config包内&lt;/p&gt; &lt;p&gt;内置：在Classpath根目录&lt;/p&gt; &lt;p&gt;同样，这个列表按照优先级排序，也就是说，src/main/resources/config下application.properties覆盖src/main/resources下application.properties中相同的属性&lt;/p&gt; &lt;p&gt;如果你在相同优先级位置同时有application.properties和application.yml，那么application.yml里面的属性就会覆盖application.properties里的属性。&lt;/p&gt; &lt;h3&gt;Profile-多环境配置&lt;/h3&gt; &lt;p&gt;当应用程序需要部署到不同运行环境时，一些配置细节通常会有所不同，最简单的比如日志，生产日志会将日志级别设置为WARN或更高级别，并将日志写入日志文件，而开发的时候需要日志级别为DEBUG，日志输出到控制台即可。&lt;/p&gt; &lt;p&gt;如果按照以前的做法，就是每次发布的时候替换掉配置文件，这样太麻烦了，Spring Boot的Profile就给我们提供了解决方案，命令带上参数就搞定。&lt;/p&gt; &lt;p&gt;在Spring Boot中多环境配置文件名需要满足application-{profile}.properties的格式，其中{profile}对应你的环境标识，比如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;application-dev.properties：开发环境 application-prod.properties：生产环境 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;想要使用对应的环境，只需要在application.properties中使用spring.profiles.active属性来设置，值对应上面提到的{profile}，这里就是指dev、prod这2个。&lt;/p&gt; &lt;p&gt;或者用命令行启动的时候带上参数： java -jar xxx.jar --spring.profiles.active=dev&lt;/p&gt; &lt;h2&gt;SpringBoot系列-3启动解析&lt;/h2&gt; &lt;p&gt;1、@SpringBootApplication 2、SpringBootApplication.run()&lt;/p&gt; &lt;h3&gt;SpringBoot执行流程&lt;/h3&gt; &lt;p&gt;SpringApplication的run方法的实现程序运行的主要线路，该方法的主要流程大体可以归纳如下：&lt;/p&gt; &lt;p&gt;1） 如果我们使用的是SpringApplication的静态run方法，那么，这个方法里面首先要创建一个SpringApplication对象实例，然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候，它会提前做几件事情： 根据classpath里面是否存在某个特征类（org.springframework.web.context.ConfigurableWebApplicationContext）来决定是否应该创建一个为Web应用使用的ApplicationContext类型。 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。 推断并设置main方法的定义类。&lt;/p&gt; &lt;p&gt;2） SpringApplication实例初始化完成并且完成设置后，就开始执行run方法的逻辑了，方法执行伊始，首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法，告诉这些SpringApplicationRunListener，“嘿，SpringBoot应用要开始执行咯！”。&lt;/p&gt; &lt;p&gt;3） 创建并配置当前Spring Boot应用将要使用的Environment（包括配置要使用的PropertySource以及Profile）。&lt;/p&gt; &lt;p&gt;4） 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法，告诉他们：“当前SpringBoot应用使用的Environment准备好了咯！”。&lt;/p&gt; &lt;p&gt;5） 如果SpringApplication的showBanner属性被设置为true，则打印banner。&lt;/p&gt; &lt;p&gt;6） 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果，决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成，然后根据条件决定是否添加ShutdownHook，决定是否使用自定义的BeanNameGenerator，决定是否使用自定义的ResourceLoader，当然，最重要的，将之前准备好的Environment设置给创建好的ApplicationContext使用。&lt;/p&gt; &lt;p&gt;7） ApplicationContext创建好之后，SpringApplication会再次借助Spring-FactoriesLoader，查找并加载classpath中所有可用的ApplicationContext-Initializer，然后遍历调用这些ApplicationContextInitializer的initialize（applicationContext）方法来对已经创建好的ApplicationContext进行进一步的处理。&lt;/p&gt; &lt;p&gt;8） 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。&lt;/p&gt; &lt;p&gt;9） 最核心的一步，将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。&lt;/p&gt; &lt;p&gt;10） 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。&lt;/p&gt; &lt;p&gt;11） 调用ApplicationContext的refresh()方法，完成IoC容器可用的最后一道工序。&lt;/p&gt; &lt;p&gt;12） 查找当前ApplicationContext中是否注册有CommandLineRunner，如果有，则遍历执行它们。&lt;/p&gt; &lt;p&gt;13） 正常情况下，遍历执行SpringApplicationRunListener的finished()方法、（如果整个过程出现异常，则依然调用所有SpringApplicationRunListener的finished()方法，只不过这种情况下会将异常信息一并传入处理）&lt;/p&gt; &lt;h2&gt;SpringBoot系列-4Web应用&lt;/h2&gt; &lt;h3&gt;项目结构推荐&lt;/h3&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/blogimg/SpringBoot3.png" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;root package结构：com.example.demo应用启动类&lt;/p&gt; &lt;p&gt;Application.java置于root package下，这样使用&lt;/p&gt; &lt;p&gt;@ComponentScan注解的时候默认就扫描当前所在类的package&lt;/p&gt; &lt;p&gt;实体（Entity）置于com.example.demo.domain包下&lt;/p&gt; &lt;p&gt;逻辑层（Service）置于com.example.demo.service包下&lt;/p&gt; &lt;p&gt;controller层（web）置于com.example.demo.controller层包下&lt;/p&gt; &lt;p&gt;static可以用来存放静态资源&lt;/p&gt; &lt;p&gt;templates用来存放默认的模板配置路径&lt;/p&gt; &lt;h3&gt;Spring MVC自动配置&lt;/h3&gt; &lt;p&gt;Spring Boot为Spring MVC提供适用于多数应用的自动配置功能。在Spring默认基础上，自动配置添加了以下特性：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;   1、引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。    2、对静态资源的支持，包括对WebJars的支持。    3、自动注册Converter，GenericConverter，Formatter beans。    4、对HttpMessageConverters的支持。    5、自动注册MessageCodeResolver。    6、对静态index.html的支持。    7、对自定义Favicon的支持。 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果想全面控制Spring MVC，你可以添加自己的@Configuration，并使用@EnableWebMvc对其注解。如果想保留Spring Boot MVC的特性，并只是添加其他的MVC配置(拦截器，formatters，视图控制器等)，你可以添加自己的WebMvcConfigurerAdapter类型的@Bean（不使用@EnableWebMvc注解）&lt;/p&gt; &lt;h3&gt;静态文件&lt;/h3&gt; &lt;p&gt;默认情况下，Spring Boot从classpath下一个叫/static（/public，/resources或/META-INF/resources）的文件夹或从ServletContext根目录提供静态内容。这使用了Spring MVC的ResourceHttpRequestHandler，所以你可以通过添加自己的WebMvcConfigurerAdapter并覆写addResourceHandlers方法来改变这个行为（加载静态文件）。&lt;/p&gt; &lt;p&gt;在一个单独的web应用中，容器默认的servlet是开启的，如果Spring决定不处理某些请求，默认的servlet作为一个回退（降级）将从ServletContext根目录加载内容。大多数时候，这不会发生（除非你修改默认的MVC配置），因为Spring总能够通过DispatcherServlet处理请求。&lt;/p&gt; &lt;p&gt;此外，上述标准的静态资源位置有个例外情况是Webjars内容。任何在/webjars/**路径下的资源都将从jar文件中提供，只要它们以Webjars的格式打包。&lt;/p&gt; &lt;p&gt;注：如果你的应用将被打包成jar，那就不要使用src/main/webapp文件夹。尽管该文件夹是一个共同的标准，但它仅在打包成war的情况下起作用，并且如果产生一个jar，多数构建工具都会忽略它&lt;/p&gt; &lt;h3&gt;默认资源映射&lt;/h3&gt; &lt;p&gt;Spring Boot 默认为我们提供了静态资源处理，使用 WebMvcAutoConfiguration 中的配置各种属性。 建议大家使用Spring Boot的默认配置方式，提供的静态资源映射如下:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;classpath:/META-INF/resources classpath:/resources classpath:/static classpath:/public 优先级顺序为：META-INF/resources &amp;gt; resources &amp;gt; static &amp;gt; public 可以在application.properties中修改： # 默认值为 /** spring.mvc.static-path-pattern # 默认值为 classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ spring.resources.static-locations=这里设置要指向的路径，多个使用英文逗号隔开 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;模板引擎&lt;/h3&gt; &lt;p&gt;Spring Boot支持多种模版引擎包括：&lt;/p&gt; &lt;p&gt;1、FreeMarker&lt;/p&gt; &lt;p&gt;2、Groovy&lt;/p&gt; &lt;p&gt;3、Thymeleaf(官方推荐)&lt;/p&gt; &lt;p&gt;4、Mustache&lt;/p&gt; &lt;p&gt;JSP技术Spring Boot官方是不推荐的，原因有三：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  1、tomcat只支持war的打包方式，不支持可执行的jar。   2、Jetty 嵌套的容器不支持jsp   3、Undertow 创建自定义error.jsp页面不会覆盖错误处理的默认视图，而应该使用自定义错误页面 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当你使用上述模板引擎中的任何一个，它们默认的模板配置路径为：src/main/resources/templates。当然也可以修改这个路径，具体如何修改，可在各模板引擎的配置属性中查询并修改。&lt;/p&gt; &lt;h2&gt;SpringBoot系列-5数据库&lt;/h2&gt; &lt;h3&gt;Mybatis整合&lt;/h3&gt; &lt;p&gt;SpringBoot集成 https://github.com/mybatis/spring-boot-starter/wiki/Quick-Start&lt;/p&gt; &lt;p&gt;Mybatis分页插件： https://github.com/pagehelper/Mybatis-PageHelper&lt;/p&gt;</content:encoded>
      <pubDate>Fri, 10 Nov 2017 07:57:27 GMT</pubDate>
    </item>
    <item>
      <title>JDK自带工具之概览</title>
      <link>https://maruifu.cn/article/51</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;在我们平常对java程序进行问题排查、性能调优时，如果没有合适的工具，很多时候会事倍功半，甚至无法继续进行下去。其实，jdk自身已经提供了很多强大的工具供我们使用。本文就对这些工具做一个概览性的描述&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;小马哥的JDK版本是&lt;/p&gt; &lt;pre&gt;&lt;code&gt;C:\Users\XiaoMage&amp;gt;java -version   java version &amp;quot;1.8.0_91&amp;quot;   Java(TM) SE Runtime Environment (build 1.8.0_91-b15)   Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;JAVA_HOME/bin下的工具有&lt;/p&gt; &lt;pre&gt;&lt;code&gt; C:\Program Files\Java\jdk1.8.0_91\bin 的目录  [.]                  jconsole.exe         klist.exe [..]                 jdb.exe              ktab.exe appletviewer.exe     jdeps.exe            msvcr100.dll extcheck.exe         jhat.exe             native2ascii.exe idlj.exe             jinfo.exe            orbd.exe jabswitch.exe        jjs.exe              pack200.exe jar.exe              jli.dll              policytool.exe jarsigner.exe        jmap.exe             rmic.exe java-rmi.exe         jmc.exe              rmid.exe java.exe             jmc.ini              rmiregistry.exe javac.exe            jps.exe              schemagen.exe javadoc.exe          jrunscript.exe       serialver.exe javafxpackager.exe   jsadebugd.exe        servertool.exe javah.exe            jstack.exe           tnameserv.exe javap.exe            jstat.exe            unpack200.exe javapackager.exe     jstatd.exe           wsgen.exe javaw.exe            jvisualvm.exe        wsimport.exe javaws.exe           keytool.exe          xjc.exe jcmd.exe             kinit.exe               54 个文件      3,432,305 字节                2 个目录 39,509,975,040 可用字节  &lt;/code&gt;&lt;/pre&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;工具名称&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;appletviewer.exe&lt;/td&gt;&lt;td&gt;用于运行并浏览applet小程序。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;apt.exe&lt;/td&gt;&lt;td&gt;注解处理工具(Annotation Processing Tool)，主要用于注解处理。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;extcheck.exe&lt;/td&gt;&lt;td&gt;扩展检测工具，主要用于检测指定jar文件与当前已安装的Java SDK扩展之间是否存在版本冲突。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;idlj.exe&lt;/td&gt;&lt;td&gt;IDL转Java编译器(IDL-to-Java Compiler)，用于为指定的IDL文件生成Java绑定。IDL意即接口定义语言(Interface Definition Language)。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;jabswitch.exe&lt;/td&gt;&lt;td&gt;Java访问桥开关(Java Access Bridge switch)，用于启用/禁用Java访问桥。Java访问桥内置于Java 7 Update 6及以上版本，主要为Windows系统平台提供一套访问Java应用的API。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;jar.exe&lt;/td&gt;&lt;td&gt;jar文件管理工具，主要用于打包压缩、解压jar文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;jarsigner.exe&lt;/td&gt;&lt;td&gt;jar密匙签名工具。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;java.exe&lt;/td&gt;&lt;td&gt;Java运行工具，用于运行.class字节码文件或.jar文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;javac.exe&lt;/td&gt;&lt;td&gt;Java编译工具(Java Compiler)，用于编译Java源代码文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;javadoc.exe&lt;/td&gt;&lt;td&gt;Java文档工具，主要用于根据Java源代码中的注释信息生成HTML格式的API帮助文档。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;javafxpackager.exe&lt;/td&gt;&lt;td&gt;JavaFX包装器，用于执行与封装或签名JavaFX应用有关的任务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;javah.exe&lt;/td&gt;&lt;td&gt;Java头文件工具，用于根据Java类生成C/C++头文件和源文件(主要用于JNI开发领域)。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;javap.exe&lt;/td&gt;&lt;td&gt;Java反编译工具，主要用于根据Java字节码文件反汇编为Java源代码文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;14&lt;/td&gt;&lt;td&gt;java-rmi.exe&lt;/td&gt;&lt;td&gt;Java远程方法调用(Java Remote Method Invocation)工具，主要用于在客户机上调用远程服务器上的对象。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;javaw.exe&lt;/td&gt;&lt;td&gt;Java运行工具，用于运行.class字节码文件或.jar文件，但不会显示控制台输出信息，适用于运行图形化程序。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;javaws.exe&lt;/td&gt;&lt;td&gt;Java Web Start，使您可以从Web下载和运行Java应用程序，下载、安装、运行、更新Java应用程序都非常简单方便。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;jcmd.exe&lt;/td&gt;&lt;td&gt;Java 命令行(Java Command)，用于向正在运行的JVM发送诊断命令请求。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;jconsole.exe&lt;/td&gt;&lt;td&gt;图形化用户界面的监测工具，主要用于监测并显示运行于Java平台上的应用程序的性能和资源占用等信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;jdb.exe&lt;/td&gt;&lt;td&gt;Java调试工具(Java Debugger)，主要用于对Java应用进行断点调试。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;jhat.exe&lt;/td&gt;&lt;td&gt;Java堆分析工具(Java Heap Analysis Tool)，用于分析Java堆内存中的对象信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;jinfo.exe&lt;/td&gt;&lt;td&gt;Java配置信息工具(Java Configuration Information)，用于打印指定Java进程、核心文件或远程调试服务器的配置信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;jmap.exe&lt;/td&gt;&lt;td&gt;Java内存映射工具(Java Memory Map)，主要用于打印指定Java进程、核心文件或远程调试服务器的共享对象内存映射或堆内存细节。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;23&lt;/td&gt;&lt;td&gt;jmc.exe&lt;/td&gt;&lt;td&gt;Java任务控制工具(Java Mission Control)，主要用于HotSpot JVM的生产时间监测、分析、诊断。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;jps.exe&lt;/td&gt;&lt;td&gt;JVM进程状态工具(JVM Process Status Tool)，用于显示目标系统上的HotSpot JVM的Java进程信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;25&lt;/td&gt;&lt;td&gt;jrunscript.exe&lt;/td&gt;&lt;td&gt;Java命令行脚本外壳工具(command line script shell)，主要用于解释执行javascript、groovy、ruby等脚本语言。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;jsadebugd.exe&lt;/td&gt;&lt;td&gt;Java可用性代理调试守护进程(Java Serviceability Agent Debug Daemon)，主要用于附加到指定的Java进程、核心文件，或充当一个调试服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;27&lt;/td&gt;&lt;td&gt;jstack.exe&lt;/td&gt;&lt;td&gt;Java堆栈跟踪工具，主要用于打印指定Java进程、核心文件或远程调试服务器的Java线程的堆栈跟踪信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;28&lt;/td&gt;&lt;td&gt;jstat.exe&lt;/td&gt;&lt;td&gt;JVM统计监测工具(JVM Statistics Monitoring Tool)，主要用于监测并显示JVM的性能统计信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;jstatd.exe&lt;/td&gt;&lt;td&gt;jstatd(VM jstatd Daemon)工具是一个RMI服务器应用，用于监测HotSpot JVM的创建和终止，并提供一个接口，允许远程监测工具附加到运行于本地主机的JVM上。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;jvisualvm.exe&lt;/td&gt;&lt;td&gt;JVM监测、故障排除、分析工具，主要以图形化界面的方式提供运行于指定虚拟机的Java应用程序的详细信息。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;31&lt;/td&gt;&lt;td&gt;keytool.exe&lt;/td&gt;&lt;td&gt;密钥和证书管理工具，主要用于密钥和证书的创建、修改、删除等。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;kinit.exe&lt;/td&gt;&lt;td&gt;主要用于获取或缓存Kerberos协议的票据授权票据。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;33&lt;/td&gt;&lt;td&gt;klist.exe&lt;/td&gt;&lt;td&gt;允许用户查看本地凭据缓存和密钥表中的条目(用于Kerberos协议)。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;ktab.exe&lt;/td&gt;&lt;td&gt;Kerberos密钥表管理工具，允许用户管理存储于本地密钥表中的主要名称和服务密钥。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;35&lt;/td&gt;&lt;td&gt;native2ascii.exe&lt;/td&gt;&lt;td&gt;本地编码到ASCII编码的转换器(Native-to-ASCII Converter)，用于&amp;quot;任意受支持的字符编码&amp;quot;和与之对应的&amp;quot;ASCII编码和(或)Unicode转义&amp;quot;之间的相互转换。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;orbd.exe&lt;/td&gt;&lt;td&gt;对象请求代理守护进程(Object Request Broker Daemon)，它使客户端能够透明地定位和调用位于CORBA环境的服务器上的持久对象。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;pack200.exe&lt;/td&gt;&lt;td&gt;JAR文件打包压缩工具，它可以利用Java类特有的结构，对普通JAR文件进行高效压缩，以便于能够更快地进行网络传输。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;38&lt;/td&gt;&lt;td&gt;packager.exe&lt;/td&gt;&lt;td&gt;这是微软提供的对象包装程序，用于对象安装包。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;39&lt;/td&gt;&lt;td&gt;policytool.exe&lt;/td&gt;&lt;td&gt;策略工具，用于管理用户策略文件(.java.policy)。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;40&lt;/td&gt;&lt;td&gt;rmic.exe&lt;/td&gt;&lt;td&gt;Java RMI 编译器，为使用JRMP或IIOP协议的远程对象生成stub、skeleton、和tie类，也用于生成OMG IDL。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;41&lt;/td&gt;&lt;td&gt;rmid.exe&lt;/td&gt;&lt;td&gt;Java RMI 激活系统守护进程，rmid启动激活系统守护进程，允许在虚拟机中注册或激活对象。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;42&lt;/td&gt;&lt;td&gt;rmiregistry.exe&lt;/td&gt;&lt;td&gt;Java 远程对象注册表，用于在当前主机的指定端口上创建并启动一个远程对象注册表。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;43&lt;/td&gt;&lt;td&gt;schemagen.exe&lt;/td&gt;&lt;td&gt;XML schema生成器，用于生成XML schema文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;44&lt;/td&gt;&lt;td&gt;serialver.exe&lt;/td&gt;&lt;td&gt;序列版本命令，用于生成并返回serialVersionUID。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;45&lt;/td&gt;&lt;td&gt;servertool.exe&lt;/td&gt;&lt;td&gt;Java IDL 服务器工具，用于注册、取消注册、启动和终止持久化的服务器。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;46&lt;/td&gt;&lt;td&gt;tnameserv.exe&lt;/td&gt;&lt;td&gt;Java IDL瞬时命名服务。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;47&lt;/td&gt;&lt;td&gt;unpack200.exe&lt;/td&gt;&lt;td&gt;JAR文件解压工具，将一个由pack200打包的文件解压提取为JAR文件。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;48&lt;/td&gt;&lt;td&gt;wsgen.exe&lt;/td&gt;&lt;td&gt;XML Web Service 2.0的Java API，生成用于JAX-WS Web Service的JAX-WS便携式产物。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;49&lt;/td&gt;&lt;td&gt;wsimport.exe&lt;/td&gt;&lt;td&gt;XML Web Service 2.0的Java API，主要用于根据服务端发布的wsdl文件生成客户端存根及框架&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;xjc.exe&lt;/td&gt;&lt;td&gt;主要用于根据XML schema文件生成对应的Java类。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;比较常用的是：&lt;/p&gt; &lt;p&gt;jvisualvm.exe&lt;br /&gt; jconsole.exe&lt;/p&gt; &lt;p&gt;参考资料&lt;/p&gt; &lt;p&gt;&lt;a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html" title="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html" target="_blank"&gt;https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Nov 2017 12:40:58 GMT</pubDate>
    </item>
    <item>
      <title>[译]Java中9个处理Exception的最佳实践</title>
      <link>https://maruifu.cn/article/50</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解，即使一些有经验的开发者也需要花费很多时间来思考如何处理异常，包括需要处理哪些异常，怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范对异常的处理的原因。而团队之间的这些规范往往是截然不同的。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;本文给出几个被很多团队使用的异常处理最佳实践。&lt;/p&gt; &lt;h3&gt;在Finally块中清理资源&lt;/h3&gt; &lt;p&gt;当使用类似InputStream这种需要使用后关闭的资源时，一个常见的错误就是在try块的最后关闭资源。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void doNotCloseResourceInTry() {     FileInputStream inputStream = null;     try {         File file = new File(&amp;quot;./tmp.txt&amp;quot;);         inputStream = new FileInputStream(file);         // use the inputStream to read a file         // do NOT do this         inputStream.close();     } catch (FileNotFoundException e) {         log.error(e);     } catch (IOException e) {         log.error(e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;上述代码在没有任何exception的时候运行是没有问题的。但是当try块中的语句抛出异常或者自己实现的代码抛出异常，那么就不会执行最后的关闭语句，从而资源也无法释放。&lt;/p&gt; &lt;p&gt;合理的做法则是将所有清理的代码都放到finally块中或者使用try-with-resource语句。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void closeResourceInFinally() {     FileInputStream inputStream = null;     try {         File file = new File(&amp;quot;./tmp.txt&amp;quot;);         inputStream = new FileInputStream(file);         // use the inputStream to read a file     } catch (FileNotFoundException e) {         log.error(e);     } finally {         if (inputStream != null) {             try {                 inputStream.close();             } catch (IOException e) {                 log.error(e);             }         }     } }  public void automaticallyCloseResource() {     File file = new File(&amp;quot;./tmp.txt&amp;quot;);     try (FileInputStream inputStream = new FileInputStream(file);) {         // use the inputStream to read a file     } catch (FileNotFoundException e) {         log.error(e);     } catch (IOException e) {         log.error(e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;指定具体的异常&lt;/h3&gt; &lt;p&gt;尽可能的使用最具体的异常来声明方法，这样才能使得代码更容易理解。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void doNotDoThis() throws Exception {     ... } public void doThis() throws NumberFormatException {     ... } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如上，NumberFormatException字面上即可以看出是数字格式化错误。&lt;/p&gt; &lt;h3&gt;对异常进行文档说明&lt;/h3&gt; &lt;p&gt;当在方法上声明抛出异常时，也需要进行文档说明。和前面的一点一样，都是为了给调用者提供尽可能多的信息，从而可以更好地避免/处理异常。&lt;/p&gt; &lt;p&gt;在Javadoc中加入throws声明，并且描述抛出异常的场景。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/**  * This method does something extremely useful ...  *  * @param input  * @throws MyBusinessException if ... happens  */ public void doSomething(String input) throws MyBusinessException {     ... } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;抛出异常的时候包含描述信息&lt;/h3&gt; &lt;p&gt;在抛出异常时，需要尽可能精确地描述问题和相关信息，这样无论是打印到日志中还是监控工具中，都能够更容易被人阅读，从而可以更好地定位具体错误信息、错误的严重程度等。&lt;/p&gt; &lt;p&gt;但这里并不是说要对错误信息长篇大论，因为本来Exception的类名就能够反映错误的原因，因此只需要用一到两句话描述即可。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;try {     new Long(&amp;quot;xyz&amp;quot;); } catch (NumberFormatException e) {     log.error(e); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;NumberFormatException即告诉了这个异常是格式化错误，异常的额外信息只需要提供这个错误字符串即可。当异常的名称不够明显的时候，则需要提供尽可能具体的错误信息。&lt;/p&gt; &lt;h3&gt;首先捕获最具体的异常&lt;/h3&gt; &lt;p&gt;现在很多IDE都能智能提示这个最佳实践，当你试图首先捕获最笼统的异常时，会提示不能达到的代码。&lt;/p&gt; &lt;p&gt;当有多个catch块中，按照捕获顺序只有第一个匹配到的catch块才能执行。因此，如果先捕获IllegalArgumentException，那么则无法运行到对NumberFormatException的捕获。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void catchMostSpecificExceptionFirst() {     try {         doSomething(&amp;quot;A message&amp;quot;);     } catch (NumberFormatException e) {         log.error(e);     } catch (IllegalArgumentException e) {         log.error(e)     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;不要捕获Throwable&lt;/h3&gt; &lt;p&gt;Throwable是所有异常和错误的父类。你可以在catch语句中捕获，但是永远不要这么做。&lt;/p&gt; &lt;p&gt;如果catch了throwable，那么不仅仅会捕获所有exception，还会捕获error。而error是表明无法恢复的jvm错误。因此除非绝对肯定能够处理或者被要求处理error，不要捕获throwable。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void doNotCatchThrowable() {     try {         // do something     } catch (Throwable t) {         // don't do this!     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;不要忽略异常&lt;/h3&gt; &lt;p&gt;很多时候，开发者很有自信不会抛出异常，因此写了一个catch块，但是没有做任何处理或者记录日志。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void doNotIgnoreExceptions() {     try {         // do something     } catch (NumberFormatException e) {         // this will never happen     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但现实是经常会出现无法预料的异常或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码)，而此时由于异常被捕获，使得无法拿到足够的错误信息来定位问题。&lt;/p&gt; &lt;p&gt;合理的做法是至少要记录异常的信息。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void logAnException() {     try {         // do something     } catch (NumberFormatException e) {         log.error(&amp;quot;This should never happen: &amp;quot; + e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;不要记录并抛出异常&lt;/h3&gt; &lt;p&gt;可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;try {     new Long(&amp;quot;xyz&amp;quot;); } catch (NumberFormatException e) {     log.error(e);     throw e; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: &amp;quot;xyz&amp;quot; Exception in thread &amp;quot;main&amp;quot; java.lang.NumberFormatException: For input string: &amp;quot;xyz&amp;quot; at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.(Long.java:965) at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63) at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58) &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如上所示，后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息，那么可以将异常包装为自定义异常。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void wrapException(String input) throws MyBusinessException {     try {         // do something     } catch (NumberFormatException e) {         throw new MyBusinessException(&amp;quot;A message that describes the error.&amp;quot;, e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;因此，仅仅当想要处理异常时才去捕获，否则只需要在方法签名中声明让调用者去处理。&lt;/p&gt; &lt;h3&gt;包装异常时不要抛弃原始的异常&lt;/h3&gt; &lt;p&gt;捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。&lt;/p&gt; &lt;p&gt;需要注意的是，包装异常时，一定要把原始的异常设置为cause(Exception有构造方法可以传入cause)。否则，丢失了原始的异常信息会让错误的分析变得困难。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void wrapException(String input) throws MyBusinessException {     try {         // do something     } catch (NumberFormatException e) {         throw new MyBusinessException(&amp;quot;A message that describes the error.&amp;quot;, e);     } } &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;综上可知，当抛出或者捕获异常时，有很多不一样的东西需要考虑。其中的许多点都是为了提升代码的可阅读性或者api的可用性。&lt;/p&gt; &lt;p&gt;异常不仅仅是一个错误控制机制，也是一个沟通媒介，因此与你的协作者讨论这些最佳实践并制定一些规范能够让每个人都理解相关的通用概念并且能够按照同样的方式使用它们。&lt;/p&gt; &lt;p&gt;原文链接 :&lt;a href="https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java" target="_blank"&gt;https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java&lt;/a&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Nov 2017 11:10:54 GMT</pubDate>
    </item>
    <item>
      <title>从 Subversion 过渡到 Git</title>
      <link>https://maruifu.cn/article/49</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;目前，想从 Subversion 过渡到 Git 其实并不困难，只要你不把 Git 和 Subversion混淆就行。一旦你明白了两者在概念上的区别，这个改变的过程就会变得容易。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;分布式与集中式&lt;/h2&gt; &lt;p&gt;Subversion是一个集中式（centralized）的版本控制系统。所有的开发团队成员都工作在单一的远程中央仓库上，当在这个中央仓库上进行 “签出（checkout）” 操作时，它就会在你的本地计算机上设置一个 “工作副本（working copy）”。这就是一个存储在你本地计算机上的一个特定版本的快照。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/11/ubav6eao3sh42o2hbq4hjgluug.png" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;p&gt;Git从 Subversion过渡到GitGit是一个分部式（distributed）的版本控制系统，它有着一个不同的工作方式。相对于Subversion 的 “签出（checkout）”，每一个Git用户会从远程仓库“克隆（clone）”出一个本地仓库。反过来说，一个用户会得到一个完整的仓库，而不仅仅只是一个工作副本。用户在本地计算机上拥有自己的仓库，并且包含所有的项目历史记录。用户可以在自己的本地计算机上做任何想要操作，例如提交（commit），历史检查（inspect history），恢复到一个旧的版本等等。只有当你想要共享你的工作结果时，你才需要连接到远程服务器上。&lt;/p&gt; &lt;h2&gt;仓库结构和 URLs&lt;/h2&gt; &lt;p&gt;一个 Subversion 的仓库通常都是由几个目录组织起来的。“trunk” 目录对应你的开发主线，“branches” 目录对应那些特定的工作背景下的开发，而 “tags” 目录则用来标记一个特定的版本。它们都要通过自己的 URL 来指向到它在中央仓库中的具体位置：&lt;/p&gt; &lt;p&gt;&lt;code&gt;svn+ssh://svn@example.com/svn/trunkGit&lt;/code&gt;&lt;/p&gt; &lt;p&gt;仓库就完全不一样了，它的组成完全就是一个在项目根目录下的 “.git” 文件夹。对分支和标记的查找完全依靠命令，而不是通过 URLs。Git 的 URL 只指向仓库的位置。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ssh://git@example.com/path/to/git-repo.git &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;分支&lt;/h2&gt; &lt;p&gt;正如刚才提到的， Subversion 的分支仅仅是一些有特殊含义的目录。在创建一个新的分支时，你只是把项目的当前状态完完整整地拷贝到这个新的分支目录中。&lt;/p&gt; &lt;p&gt;Git 的分支技术是它的设计核心，因此它拥有一个完全不同的概念。一个在 Git 中的分支就是一个指向一个特定版本的指针：不拷贝任何文件；不创建任何目录；没有任何额外的操作。在 Git 中你永远工作在一个分支上，至少工作在那个系统默认创建的 “master” 分支上。在你的工作副本上只包括你当前的活动分支中的文件（ Git 称之为 “HEAD”）。所有其他的版本和分支都被保存在你的本地仓库中，并且随时都可以非常快速地恢复到一个旧的版本。一定要记住 Git 的分布式特性：分支可以被发布到在远程服务器上，但是本地上的分支对于日常的工作更加重要。&lt;/p&gt; &lt;h2&gt;提交&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;当你想要在 Subversion 中提交一个改动，有如下的一些规则：&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;你必须确保与中央仓库的连接。你不能进行离线提交。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;提交的内容要立即存储在中央仓库中。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;它会被分配一个递增版本号。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;提交在 Git 中就是完全另外一种情况：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;你没有必要连接到任何一个 “中央” 仓库，因为在你的计算机中就拥有一个完整的本地仓库。因此提交仅仅只记录在本地仓库上。它们不会自动地传递到远程仓库中，除非你自己决定共享这个改动。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;文件的改动并不意味着它会被自动地包含在下一次提交中。你必须指明哪些改动你想要提交，并把它添加的所谓的 “暂存区（Staging Area）”中。你甚至可以只对文件的部分修改或是特定的几行代码进行提交，而其他部分则稍后提交。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;“commit hashes” 替代了版本号码。由于提交都发生在开发人员的本地计算机上，你不可能给某个提交分配一个号码 #5，而另外一个分配 #6，这就产生了个问题，在分布式系统下谁是第一个提交呢？在 Git 中，每一个提交必须拥有一个唯一的ID，因此一个哈希字符串就代替了那个依次递增的版本号。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;分享工作&lt;/h2&gt; &lt;p&gt;在 Subversion 中，在提交之后，你的工作会被自动地转移到中央仓库上去。只有在你连接到这个中央服务器时你才可以进行提交。&lt;/p&gt; &lt;p&gt;不会自动上传任何东西。你可以自己决定，你的那些分支（也可能是所有分支）需要共享给你其他的团队成员。除此之外共享工作也是十分安全的。冲突只会出现在你的本地上，它决不可能发生在远程服务器上。这会让你有信心来解决冲突，因为你不会破坏远程仓库。&lt;/p&gt; &lt;h2&gt;为什么选择 Git&lt;/h2&gt; &lt;p&gt;虽然市场上有几十种不同的版本控制系统，一些世界上最著名的项目（例如 Linux 内核，Ruby on Rails，或是jQuery）都选择了使用 Git 作为它们的版本控制系统。为什么它们都选择 Git 呢？&lt;/p&gt; &lt;h3&gt;节省时间Git&lt;/h3&gt; &lt;p&gt;运行快速。尽管我们在这里讨论的只是运行一个命令所需要的几秒钟，但是把它累积在你的日常工作中就是一个不小的飞跃了。它可以节省那些不必要的等待时间，并且去完成其它一些有意义的工作。&lt;/p&gt; &lt;h3&gt;离线工作&lt;/h3&gt; &lt;p&gt;当你不能联机远程中央仓库时你该怎么工作呢？对于一个像 Subversion 或者 CVS 的集中式版本控制系统来说，如果你没有连接到中央仓库，你就不能很好的工作。如果使用 Git ，几乎所有的东西都可以简单地在你的本地机器上完成。例如进行提交，查看你的项目历史，合并或者创建分支等等。至于在哪里工作？什么时候工作？ Git 不会给你施加任何限制。&lt;/p&gt; &lt;h3&gt;撤销错误操作&lt;/h3&gt; &lt;p&gt;每个人都会犯错，而使用 Git 的最大好处就在于，几乎在所有的情况下你都能 “撤消” 你的错误操作。比如如果你忘记了把一个小小的改动包含进来，因此你要改正你的上个提交。又或者你想要撤销一个完整的提交，因为这个功能有可能是不必要的。当发生了很严重的错误时，你甚至可以通过恢复引用日志来让一个提交不可见。你可以放心，Git 几乎很少真正地删除数据。&lt;/p&gt; &lt;h3&gt;可靠性高&lt;/h3&gt; &lt;p&gt;不用担忧，你不会在 Git 中搞砸任何东西，这种感觉是不是非常好？在你的 Git 项目中的每一个团队成员都克隆了整个项目在他们的本地计算机，这个本地克隆也可以看作一个完整的项目备份。除此之外， Git 上的操作几乎都是进行数据添加，几乎从不删除数据。这意味着丢失数据或是仓库损坏的情况几乎不可能发生。&lt;/p&gt; &lt;h3&gt;让提交更有意义&lt;/h3&gt; &lt;p&gt;只有包含了相关的改动的提交才有意义。想象一下，如果一个提交中包括一个新添加的功能 A，还包括功能 B 的一部分改动，并且还存在一个对错误 C 的修复。这样其他的团队成员就很难理解这个提交的意图，而且当其中的一个改动出现了错误，撤销起来也非常麻烦。利用它独一无二的 “暂存区（staging area）” 概念，Git 可以帮助你打造很细微和精准的提交。你可以准确地判断哪些更改将被包含在你的下一个提交中，即使只是一行改动。Git 真正提高了对版本控制的实用性。&lt;/p&gt; &lt;h3&gt;更高的自由度&lt;/h3&gt; &lt;p&gt;当使用 Git 工作时，你可以定义一个对项目和团队有意义的工作流程。使用 Git 也不需要其它的要求。你可以连接多个远程仓库，使用 rebase 来替代合并，或者在需要时可以使用子模块。当然，你也可以简单地像 Subversion 那样仅仅使用一个远程的集中式仓库。无论你使用什么样的工作流程，它都有各种各样的优点。&lt;/p&gt; &lt;h3&gt;避免混乱&lt;/h3&gt; &lt;p&gt;关注点分离可以更明确地了解事情的进程。当你工作在功能 A 上时，不应该有任何人受到你未完成的代码的影响。如果那个功能是完全没有必要的话呢？或是完成了对它的一些改动提交后，你注意到你完全错了呢？分支功能就可以解决这些问题。当然其他版本控制系统也都有分支，但是 Git 真正的把它改进地更快速，更简单了。&lt;/p&gt; &lt;h3&gt;顺应潮流&lt;/h3&gt; &lt;p&gt;聪明的开发人员应该顺应潮流。Git 正在被越来越多的知名公司和开源项目所使用，如 RubyOn Rails，jQuery，Perl，Debian，Linux 内核等等。拥有一个大型的用户群体是一个很大优势，因为往往会存在很多系统去推动他的发展。大量的教程，工具和服务，这让Git更加具有吸引力。&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 07 Nov 2017 09:08:26 GMT</pubDate>
    </item>
    <item>
      <title>Java transient关键字</title>
      <link>https://maruifu.cn/article/47</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;虽然自己挺了解Java的，但很多Java基础知识都不知道，比如transient关键字以前都没用到过，所以不知道它的作用是什么，今天看了一下项目代码中用到这个关键字了.于是整理学习一下.&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h2&gt;1. transient的作用及使用方法&lt;/h2&gt; &lt;p&gt;我们都知道一个对象只要实现了Serilizable接口，这个对象就可以被序列化，java的这种序列化模式为开发者提供了很多便利，我们可以不必关系具体序列化的过程，只要这个类实现了Serilizable接口，这个类的所有属性和方法都会自动序列化。&lt;/p&gt; &lt;p&gt;然而在实际开发过程中，我们常常会遇到这样的问题，这个类的有些属性需要序列化，而其他属性不需要被序列化，打个比方，如果一个用户有一些敏感信息（如密码，银行卡号等），为了安全起见，不希望在网络操作（主要涉及到序列化操作，本地序列化缓存也适用）中被传输，这些信息对应的变量就可以加上transient关键字。换句话说，这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。&lt;/p&gt; &lt;p&gt;总之，java 的transient关键字为我们提供了便利，你只需要实现Serilizable接口，将不需要序列化的属性前添加关键字transient，序列化对象的时候，这个属性就不会序列化到指定的目的地中。&lt;/p&gt; &lt;p&gt;示例code如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;  /**  * @description 使用transient关键字不序列化某个变量  *        注意读取的时候，读取数据的顺序一定要和存放数据的顺序保持一致  *          * @author MaRuifu  * @date  2017-11-04  */ public class TransientTest {          public static void main(String[] args) {                  User user = new User();         user.setUsername(&amp;quot;MaRuifu&amp;quot;);         user.setPasswd(&amp;quot;123456&amp;quot;);                  System.out.println(&amp;quot;read before Serializable: &amp;quot;);         System.out.println(&amp;quot;username: &amp;quot; + user.getUsername());         System.err.println(&amp;quot;password: &amp;quot; + user.getPasswd());                  try {             ObjectOutputStream os = new ObjectOutputStream(                     new FileOutputStream(&amp;quot;C:/user.txt&amp;quot;));             os.writeObject(user); // 将User对象写进文件             os.flush();             os.close();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }         try {             ObjectInputStream is = new ObjectInputStream(new FileInputStream(                     &amp;quot;C:/user.txt&amp;quot;));             user = (User) is.readObject(); // 从流中读取User的数据             is.close();                          System.out.println(&amp;quot;\nread after Serializable: &amp;quot;);             System.out.println(&amp;quot;username: &amp;quot; + user.getUsername());             System.err.println(&amp;quot;password: &amp;quot; + user.getPasswd());                      } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         } catch (ClassNotFoundException e) {             e.printStackTrace();         }     } }  class User implements Serializable {     private static final long serialVersionUID = 8294180014912103005L;            private String username;     private transient String passwd;          public String getUsername() {         return username;     }          public void setUsername(String username) {         this.username = username;     }          public String getPasswd() {         return passwd;     }          public void setPasswd(String passwd) {         this.passwd = passwd;     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出为：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;read before Serializable:  username: MaRuifu password: 123456  read after Serializable:  username: MaRuifu password: null &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;密码字段为null，说明反序列化时根本没有从文件中获取到信息。&lt;/p&gt; &lt;h2&gt;2. transient使用小结&lt;/h2&gt; &lt;p&gt;1）一旦变量被transient修饰，变量将不再是对象持久化的一部分，该变量内容在序列化后无法获得访问。&lt;/p&gt; &lt;p&gt;2）transient关键字只能修饰变量，而不能修饰方法和类。注意，本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量，则该类需要实现Serializable接口。&lt;/p&gt; &lt;p&gt;3）被transient关键字修饰的变量不再能被序列化，一个静态变量不管是否被transient修饰，均不能被序列化。&lt;/p&gt; &lt;p&gt;第三点可能有些人很迷惑，因为发现在User类中的username字段前加上static关键字后，程序运行结果依然不变，即static类型的username也读出来为“Alexia”了，这不与第三点说的矛盾吗？实际上是这样的：第三点确实没错（一个静态变量不管是否被transient修饰，均不能被序列化），反序列化后类中static型变量username的值为当前JVM中对应static变量的值，这个值是JVM中的不是反序列化得出的，不相信？好吧，下面我来证明：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;  /**  * @description 使用transient关键字不序列化某个变量  *        注意读取的时候，读取数据的顺序一定要和存放数据的顺序保持一致  *          * @author MaRuifu  * @date  2017-11-04  */ public class TransientTest {          public static void main(String[] args) {                  User user = new User();         user.setUsername(&amp;quot;MaRuifu&amp;quot;);         user.setPasswd(&amp;quot;123456&amp;quot;);                  System.out.println(&amp;quot;read before Serializable: &amp;quot;);         System.out.println(&amp;quot;username: &amp;quot; + user.getUsername());         System.err.println(&amp;quot;password: &amp;quot; + user.getPasswd());                  try {             ObjectOutputStream os = new ObjectOutputStream(                     new FileOutputStream(&amp;quot;C:/user.txt&amp;quot;));             os.writeObject(user); // 将User对象写进文件             os.flush();             os.close();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }         try {             // 在反序列化之前改变username的值             User.username = &amp;quot;XiaoMage&amp;quot;;                          ObjectInputStream is = new ObjectInputStream(new FileInputStream(                     &amp;quot;C:/user.txt&amp;quot;));             user = (User) is.readObject(); // 从流中读取User的数据             is.close();                          System.out.println(&amp;quot;\nread after Serializable: &amp;quot;);             System.out.println(&amp;quot;username: &amp;quot; + user.getUsername());             System.err.println(&amp;quot;password: &amp;quot; + user.getPasswd());                      } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         } catch (ClassNotFoundException e) {             e.printStackTrace();         }     } }  class User implements Serializable {     private static final long serialVersionUID = 8294180014912103005L;            public static String username;     private transient String passwd;          public String getUsername() {         return username;     }          public void setUsername(String username) {         this.username = username;     }          public String getPasswd() {         return passwd;     }          public void setPasswd(String passwd) {         this.passwd = passwd;     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果为:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;read before Serializable:  username: MaRuifu password: 123456  read after Serializable:  username: XiaoMage password: null &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这说明反序列化后类中static型变量username的值为当前JVM中对应static变量的值，为修改后XiaoMage，而不是序列化时的值MaRuifu。&lt;/p&gt; &lt;h2&gt;3.transient使用细节&lt;/h2&gt; &lt;p&gt;—被transient关键字修饰的变量真的不能被序列化吗？&lt;/p&gt; &lt;p&gt;思考下面的例子：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt; import java.io.Externalizable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream;  /**  * @descripiton Externalizable接口的使用  *   * @author MaRuifu  * @date 2013-10-15  *  */ public class ExternalizableTest implements Externalizable {      private transient String content = &amp;quot;是的，我将会被序列化，不管我是否被transient关键字修饰&amp;quot;;      @Override     public void writeExternal(ObjectOutput out) throws IOException {         out.writeObject(content);     }      @Override     public void readExternal(ObjectInput in) throws IOException,             ClassNotFoundException {         content = (String) in.readObject();     }      public static void main(String[] args) throws Exception {                  ExternalizableTest et = new ExternalizableTest();         ObjectOutput out = new ObjectOutputStream(new FileOutputStream(                 new File(&amp;quot;test&amp;quot;)));         out.writeObject(et);          ObjectInput in = new ObjectInputStream(new FileInputStream(new File(                 &amp;quot;test&amp;quot;)));         et = (ExternalizableTest) in.readObject();         System.out.println(et.content);          out.close();         in.close();     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;content变量会被序列化吗？好吧，我把答案都输出来了，是的，运行结果就是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;是的，我将会被序列化，不管我是否被transient关键字修饰 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是为什么呢，不是说类的变量被transient关键字修饰以后将不能序列化了吗？&lt;/p&gt; &lt;p&gt;我们知道在Java中，对象的序列化可以通过实现两种接口来实现，若实现的是Serializable接口，则所有的序列化将会自动进行，若实现的是Externalizable接口，则没有任何东西可以自动序列化，需要在writeExternal方法中进行手工指定所要序列化的变量，这与是否被transient修饰无关。因此第二个例子输出的是变量content初始化的内容，而不是null。&lt;/p&gt;</content:encoded>
      <pubDate>Sat, 04 Nov 2017 02:27:07 GMT</pubDate>
    </item>
    <item>
      <title>一小时教你学会 Maven 项目的构建与管理(3)</title>
      <link>https://maruifu.cn/article/46</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Maven翻译成中文是“专家、内行”。Maven是Apache组织中一个颇为成功的开源项目，Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理的优秀工具。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;img alt="xxx" src="" border="0" style="display:none;"/&gt; ## 用 nexus 搭建 Maven 私服 ## &lt;p&gt;Nexus下载地址：&lt;a href="http://www.sonatype.org/nexus/archived/" title="http://www.sonatype.org/nexus/archived/" target="_blank"&gt;http://www.sonatype.org/nexus/archived/&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;Nexus安装启动与使用&lt;/h3&gt; &lt;p&gt;公司内部大部分人的电脑不能访问公网，不能从maven的中央仓库下载依赖，因此找一台有公网权限的机器搭建nexus私服，其他项目组人员连接到这个私服上即可。1.节省了下载jar包依赖的过程，不必每个人都去下载jar包的依赖&lt;/p&gt; &lt;p&gt;软件准备：jdk1.7、maven-3.5.0、Nexus 2.12.0-01&lt;/p&gt; &lt;h4&gt;Nexus 2.12.0-01下载&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/rq6ukqcblagk8rkusal39kqf9d.png" alt="Nexus 2.12.0-01下载" title="Nexus 2.12.0-01下载" /&gt;&lt;/p&gt; &lt;p&gt;Windows平台下载zip格式 下载nexus-2.12.0-01-bundle.zip解压到本地磁盘&lt;/p&gt; &lt;p&gt;D:\develop\nexus\nexus-2.12.0-01&lt;/p&gt; &lt;p&gt;在D:\develop\nexus\nexus-2.12.0-01\bin\jsw目录下有很多不同的操作系统版本，我的系统是Win7 64位，选择windows-x86-64目录。&lt;/p&gt; &lt;h4&gt;windows-x86-64目录说明&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/2bkvr21g40ilkpvt8hk7g085io.png" alt="windows-x86-64目录说明" title="windows-x86-64目录说明" /&gt;&lt;/p&gt; &lt;p&gt;console-nexus.bat：命令行方式启动nexus服务器，窗口关闭不会注册为windows服务&lt;/p&gt; &lt;p&gt;install-nexus.bat：将nexus安装成windows服务，开机时自动启动&lt;/p&gt; &lt;p&gt;start-nexus.bat：启动nexus服务，也可以直接在管理-服务中手动启动nexus服务&lt;/p&gt; &lt;p&gt;stop-nexus.bat：停止nexus服务&lt;/p&gt; &lt;p&gt;uninstall-nexus.bat：卸载nexus服务&lt;/p&gt; &lt;p&gt;wrapper.exe：&lt;/p&gt; &lt;h4&gt;配置环境变量&lt;/h4&gt; &lt;p&gt;NEXUS_HOME= D:\develop\nexus\nexus-2.12.0-01&lt;/p&gt; &lt;p&gt;Path= ;%NEXUS_HOME%\bin\jsw\windows-x86-64&lt;/p&gt; &lt;p&gt;上述步骤成功后，nexus即安装成功，启动服务，在浏览器中访问&lt;/p&gt; &lt;p&gt;http://localhost:8081/nexus/&lt;/p&gt; &lt;p&gt;默认的用户名是 admin 密码是 admin123&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/obe7i9aubqjhgo299b010hj29j.png" alt="本地Maven私服" title="本地Maven私服" /&gt;&lt;/p&gt; &lt;p&gt;到此nexus安装成功。&lt;/p&gt; &lt;h4&gt;Nexus用户管理&lt;/h4&gt; &lt;p&gt;选择Security-Users添加用户User ID：feiyue 密码：feiyue&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/6mfrmtnpbmii0q42ie9n2rvhf4.png" alt="Nexus用户管理" title="Nexus用户管理" /&gt;&lt;/p&gt; &lt;p&gt;填写基本信息，添加角色，选择Nexus Administrator Role管理员角色。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/r5tmq13lq2hkio8na9ih4kmc91.png" alt="Nexus用户管理1" title="Nexus用户管理1" /&gt;&lt;/p&gt; &lt;h4&gt;为Nexus配置代理服务器&lt;/h4&gt; &lt;p&gt;如果机器通过配置代理才能访问外网，Nexus可以配置代理服务器，选择Administration-Servers，找到如下配置，填写代理信息。 &lt;img src="https://img.maruifu.com/images/blog/2017/10/sb34mq4p4aj9aqfl358alqfbut.png" alt="Nexus配置代理服务器" title="Nexus配置代理服务器" /&gt;&lt;/p&gt; &lt;p&gt;如果Nexus私服所在机器可以直接访问外网，则可以省略这一步 。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置repository&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在Views/Reposities中选择Reposities进行配置&lt;/p&gt; &lt;p&gt;Nexus可以配置3种类型的仓库，分别是proxy、hosted、group&lt;/p&gt; &lt;p&gt;Proxy：远程仓库的代理，比如nexus中配置了一个中央仓库的proxy，当用户向这个proxy请求一个 artifact时，proxy现在本地查找、如果找不到就到远程的中央仓库下载，起到了一个中转的作用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;配置中央库proxy&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;需要做的就是把Download Remote Indexes改为true，这样nexus才会从central repository下载索引，才能在nexus中使用artifact search的功能。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/scprp6mus0jupqv19lb349v1me.png" alt="配置中央库proxy" title="配置中央库proxy" /&gt;&lt;/p&gt; &lt;p&gt;Hosted：宿主仓库，用户可以把自己的一些构件部署到hosted中，也可以手动上传到hosted中。比如Oracle的驱动程序ojdbc6.jar在中央仓库找不到，就需要手工上传到hosted中。&lt;/p&gt; &lt;p&gt;配置hosted repository：一般会配置3个hosted repository，分别是3rd party、Snapshots、Releases，分别用来保存第三方jar（典型的比如ojdbc6.jar），项目组内部的快照、项目组内部的发布版&lt;/p&gt; &lt;p&gt;只是Deployment Policy这个选项，一般Snapshots会配置成允许，而Releases和3rd party会设置为禁止&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/rpickrbls4h5aoc20oa4ffinus.png" alt="配置中央库proxy" title="配置中央库proxy" /&gt;&lt;/p&gt; &lt;p&gt;Group：仓库组，在maven中没有这个概念，是nexus特有的。目的是将上述多个仓库聚合，对用户暴露统一的地址，用户就不必在pom中配置多个地址了，只要统一配置group即可。&lt;/p&gt; &lt;p&gt;配置group repository&lt;/p&gt; &lt;p&gt;group其实是一个虚拟的仓库，通过对实体仓库（proxy、hosted）进行聚合，对外暴露一个统一的地址 ，注意放到左边的仓库，才是会被聚合的仓库 &lt;img src="https://img.maruifu.com/images/blog/2017/10/pml0q4u156jsprcdipqa77ni51.png" alt="配置中央库proxy" title="配置中央库proxy" /&gt;&lt;/p&gt; &lt;h4&gt;仓库搜索服务&lt;/h4&gt; &lt;p&gt;常见的几个功能强大的公共Maven仓库搜索服务。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Sonatype Nexus&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;地址：&lt;a href="https://repository.sonatype.org/" title="https://repository.sonatype.org/" target="_blank"&gt;https://repository.sonatype.org/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Nexus是当前最流行的开源Maven仓库管理软件，提供了关键字搜索、类名搜索、坐标搜索、校验等功 能。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Mvnrepository&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;地址：&lt;a href="http://mvnrepository.com/" title="http://mvnrepository.com/" target="_blank"&gt;http://mvnrepository.com/&lt;/a&gt;&lt;/p&gt; &lt;p&gt;界面友好，提供基于关键字的搜索、构件下载、依赖声明代码片段。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;中央仓库检索服务&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;地址：&lt;a href="http://search.maven.org/" title="http://search.maven.org/" target="_blank"&gt;http://search.maven.org/&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;多模块项目构建实战&lt;/h2&gt; &lt;p&gt;本章节给出一个含有父项目parent以及一个多模块项目的空框架做为一个maven项目构建的一个实例，该项目的源码我放到了了github上：&lt;/p&gt; &lt;p&gt;github地址：&lt;a href="https://github.com/maruifu/maven_dev" title="https://github.com/maruifu/maven_dev" target="_blank"&gt;https://github.com/liangpengju/maven_dev&lt;/a&gt;&lt;/p&gt; &lt;p&gt;框架中只是展示Maven多模块项目构建的一种方式，没有具体的代码实现，后续有时间会给出一个demo，这里仅供参考交流。&lt;/p&gt; &lt;p&gt;包括了2个项目，一个是parent聚合项目，另外一个是后台管理分模块分层的项目，结构如下图所示。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/pdaa1j5u20jaeqr52b3qgbie9p.png" alt="多模块项目构建实战" title="多模块项目构建实战" /&gt;&lt;/p&gt; &lt;p&gt;maven_dev中包含父聚合项目gseem-parent,没有实际的内容，主要是pom.xml文件中集中定义依赖版本号、依赖包管理、插件管理、插件等可以继承的元素。&lt;/p&gt; &lt;p&gt;gseem-manage是一个多模块的聚合项目，包括4个子模块，parent都是gseem-manage&lt;/p&gt; &lt;p&gt;gseem-manage-pojo是项目实体类模块，打包方式为jar&lt;/p&gt; &lt;p&gt;gseem-manage-mapper是项目数据库操作dao模块，对应于mybatis的mapping中的xml文件，打包方式为jar，依赖于gseem-manage-pojo模块&lt;/p&gt; &lt;p&gt;gseem-manage-service是项目业务逻辑服务模块，包括接口和实现，打包方式为jar，依赖于gseem-manage-mapper模块&lt;/p&gt; &lt;p&gt;gseem-manage-web是项目的静态资源、jsp动态页面模块，包括接口和实现，打包方式为jar，依赖于gseem-manage-service模块&lt;/p&gt; &lt;p&gt;这个项目框架做到了依赖库的统一版本管理，分层代码的复用等。&lt;/p&gt; &lt;h3&gt;总结&lt;/h3&gt; &lt;p&gt;Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理的优秀工具。 本文主要从以下几个方面来对Maven工具的使用进行了讲解：&lt;/p&gt; &lt;p/&gt; █ Maven基础环境配置与Maven常用的基本命令。 &lt;p/&gt; █ Maven相关的核心概念理论：概念模型、仓库、坐标、依赖管理、聚合与继承。 &lt;p/&gt; █ 4种Maven项目的创建方式：手动创建、命令行、Eclipse IDE、Idea IDE. &lt;p/&gt; █ POM文件常用配置解析与Setting文件常用配置解析。 &lt;p/&gt; █ 使用Nexus搭建Maven私服与仓库搜索服务。 &lt;p/&gt; █ 多模块项目构建实战。 &lt;p&gt;Maven还有很多其他的功能，可以后续进行探讨。&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 29 Oct 2017 16:47:23 GMT</pubDate>
    </item>
    <item>
      <title>一小时教你学会 Maven 项目的构建与管理(2)</title>
      <link>https://maruifu.cn/article/45</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Maven翻译成中文是“专家、内行”。Maven是Apache组织中一个颇为成功的开源项目，Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理的优秀工具。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;img alt="xxx" src="" border="0" style="display:none;"/&gt; &lt;h2&gt;四种 Maven 项目创建方式&lt;/h2&gt; &lt;h3&gt;手动方式构建&lt;/h3&gt; &lt;p&gt;Maven项目由一个自己默认的配置，使用者不需要修改那些约定的内容，这就是“约定优于配置”，按照Maven项目目录约定，手动创建各个文件夹即可，一般不会使用这种方式。&lt;/p&gt; &lt;p&gt;动态web的Maven项目的目录约定如下图所示： &lt;img src="https://img.maruifu.com/images/blog/2017/10/78gda4vsaigslpn42j2n84eair.png" alt="Maven项目目录约定" title="Maven项目目录约定" /&gt;&lt;/p&gt; &lt;h3&gt;命令行&lt;/h3&gt; &lt;p&gt;本地磁盘建立一个空目录C:/maven/hello,命令行进入到hello目录，执行下面的命令 mvn archetype:generate -DgroupId=com.cloud.hellomaven -DartifactId=hellomaven-service -Dversion=1.0.0-SNAPSHOT -Dpackage=com.cloud.service&lt;/p&gt; &lt;p&gt;执行成功的话会自动在hello中创建符合maven项目约定的目录结构。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;：&lt;/p&gt; &lt;p/&gt; ◆ archetype:generate：生成maven项目骨架。 &lt;p/&gt; ◆ DgroupId指定maven坐标的groupId &lt;p/&gt; ◆ DartifactId指定maven坐标的artifactId &lt;p/&gt; ◆ Dversion指定maven坐标的version &lt;p/&gt; ◆ Dpackage指定maven项目的src下的包名 &lt;p&gt;自动创建的maven项目结构如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/21b5omovm8imqre75rj46omm3o.png" alt="Maven项目目录结构" title="Maven项目目录结构" /&gt;&lt;/p&gt; &lt;h3&gt;Eclipse IDE&lt;/h3&gt; &lt;p&gt;Eclipse提供了一个很好的插件m2eclipse无缝将Maven和Eclipse集成在一起，配置插件选择本地maven目录和配置文件setting.xml即可。&lt;/p&gt; &lt;p&gt;点击Eclipse菜单栏File-&amp;gt;New-&amp;gt;Ohter-&amp;gt;Maven，选择Maven Project，一路默认Next，有一步需要填写Group Id、Artifact Id、Version、选择打包方式Package（jar、war、pom），选择编译环境即可。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/3ivvbsdeqkgb8p3dlus563afid.png" alt="Maven项目目录结构" title="Maven项目目录结构" /&gt;&lt;/p&gt; &lt;p&gt;完成后目录结构如下：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/th3u0nhotug5srdph3qnounjdm.png" alt="eclipse创建Maven项目2" title="eclipse创建Maven项目2" /&gt;&lt;/p&gt; &lt;h3&gt;Idea IDE&lt;/h3&gt; &lt;p&gt;Idea IDE是一款很不错的开发Maven项目的IDE，创建一个maven web项目的过程如下：&lt;/p&gt; &lt;p&gt;选择File-New-Project，选择Maven，勾选Create from archetype，选择maven-archetype-webapp&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/rgo6eqljkmjajoli36k1p761a0.png" alt="IDEA创建Maven项目1" title="IDEA创建Maven项目1" /&gt;&lt;/p&gt; &lt;p&gt;点击Next，填写Group Id、Artifact Id、Version三项，&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/vgmlrit8mugggrgv2gpjjac3oc.png" alt="IDEA创建Maven项目2" title="IDEA创建Maven项目2" /&gt;&lt;/p&gt; &lt;p&gt;点击Next，选择Maven home，选择setting file,&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/rvo3quto10j2krrlpvlr2t38eq.png" alt="IDEA创建Maven项目3" title="IDEA创建Maven项目3" /&gt;&lt;/p&gt; &lt;p&gt;点击Next，填写Project name，选择项目路径即可，&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/t6a5frfrs2hpdo3kqgh037rlu8.png" alt="IDEA创建Maven项目4" title="IDEA创建Maven项目4" /&gt;&lt;/p&gt; &lt;p&gt;Finish完成maven web项目创建。&lt;/p&gt; &lt;h2&gt;Setting文件配置与POM 文件解析&lt;/h2&gt; &lt;h3&gt;Setting文件配置&lt;/h3&gt; &lt;h4&gt;配置用户范围和全局范围的setting.xml&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;全局范围&lt;/strong&gt;： ${maven.conf}/settings.xml文件可以全局定制Maven的行为，对一台机器上的所有用户有效。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;用户范围&lt;/strong&gt;： ${user.home}/.m2/settings.xml，只有当前用户才会受到该配置影响，还便于Maven的升级，Maven升级新版本时不需要触动该文件，推荐配置用户范围。&lt;/p&gt; &lt;h4&gt;设置HTTP代理&lt;/h4&gt; &lt;p&gt;公司网络需要通过安全认证的代理访问因特网，这种情况下需要为Maven配置HTTP代理，才能正常访问外部仓库下载所需要的资源。&lt;/p&gt; &lt;p&gt;在settings.xml文件中添加代理配置。&lt;/p&gt; &lt;pre&gt;&lt;code&gt; &amp;lt;proxies&amp;gt;         &amp;lt;proxy&amp;gt;           &amp;lt;id&amp;gt;myproxy&amp;lt;/id&amp;gt;           &amp;lt;active&amp;gt;true&amp;lt;/active&amp;gt;           &amp;lt;protocol&amp;gt;http&amp;lt;/protocol&amp;gt;           &amp;lt;host&amp;gt;192.0.0.100&amp;lt;/host&amp;gt;           &amp;lt;port&amp;gt;1234&amp;lt;/port&amp;gt;           &amp;lt;username&amp;gt;xxxxx&amp;lt;/username&amp;gt;           &amp;lt;password&amp;gt;xxxxx&amp;lt;/password&amp;gt;           &amp;lt;nonProxyHosts&amp;gt;*.xxx.com|xxx.org&amp;lt;/nonProxyHosts&amp;gt;         &amp;lt;/proxy&amp;gt;      &amp;lt;/proxies&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;proxies下可以添加多个proxy节点，默认第一个active为true的会生效。&lt;/p&gt; &lt;p&gt;nonProxyHost表示不需要代理访问的地址。中间的竖线分隔多个地址，此处可以使用星号作为通配符号。&lt;/p&gt; &lt;h4&gt;远程仓库的认证&lt;/h4&gt; &lt;p&gt;一些远程仓库出于安全考虑需要提供用户名、密码进行认证才能访问，这时需要配置认证信息，认证信息必须配置到setting.xml文件中，只放在本机，其他成员不可见，在setting.xml文件中添加server配置，一个servers可以配置一个或者多个server，假设一个id为feiyue-repo的仓库配置认证如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;servers&amp;gt;   &amp;lt;server&amp;gt;    &amp;lt;id&amp;gt;feiyue-repo&amp;lt;/id&amp;gt;    &amp;lt;username&amp;gt;repo-username&amp;lt;/username&amp;gt;    &amp;lt;password&amp;gt;repo-pwd&amp;lt;/password&amp;gt;   &amp;lt;/server&amp;gt; &amp;lt;/servers&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;setting.xml文件中server元素的id必须与pom.xml文件中需要认证的repository元素的id完全一致。&lt;/p&gt; &lt;h4&gt;镜像&lt;/h4&gt; &lt;p&gt;如果仓库X可以提供仓库Y存储的所有内容，则X可以被称为Y的一个镜像。镜像往往能够提供比中央仓库更快的服务，配置Maven使用镜像来代替中央仓库，编辑setting.xml文件。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;mirrors&amp;gt;  &amp;lt;mirror&amp;gt;       &amp;lt;id&amp;gt;jboss-public-repository-group&amp;lt;/id&amp;gt;       &amp;lt;mirrorOf&amp;gt;central&amp;lt;/mirrorOf&amp;gt;      &amp;lt;name&amp;gt;JBoss Public Repository Group&amp;lt;/name&amp;gt;  &amp;lt;url&amp;gt;http://repository.jboss.org/nexus/content/groups/public&amp;lt;/url&amp;gt;  &amp;lt;/mirror&amp;gt; &amp;lt;/mirrors&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;mirrorOf&lt;/strong&gt;：值为central，表示该配置为中央仓库的镜像，任何对于中央仓库的请求都会转至该镜像；&lt;/p&gt; &lt;p&gt;&lt;strong&gt;id&lt;/strong&gt;:是远程仓库的唯一标识。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;name&lt;/strong&gt;:阅读方便可自定义的名称。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;url&lt;/strong&gt;：远程仓库的地址。&lt;/p&gt; &lt;p&gt;如果需要认证，基于该id配置仓库认证。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;mirrorOf取值如下&lt;/strong&gt;：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;*：匹配所有远程仓库  external:*匹配所有远程仓库,使用localhost的除外，使用file://协议的除外，即匹配不在本机上的远程仓库；  rep1,rep2:匹配id=rep1和id=rep2的仓库，使用逗号分隔多个远程仓库；  *,!rep2：匹配所有远程仓库，rep2除外，使用感叹号将仓库从匹配中排除。 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;POM文件常用配置解析&lt;/h3&gt; &lt;h4&gt;parent父项目配置&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;parent&amp;gt;     &amp;lt;groupId&amp;gt;com.feiyue.parent&amp;lt;/groupId&amp;gt;     &amp;lt;artifactId&amp;gt;feiyue-parent&amp;lt;/artifactId&amp;gt;     &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;     &amp;lt;relativePath&amp;gt;../pom.xml&amp;lt;/relativePath&amp;gt; &amp;lt;/parent&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;project根节点下配置parent节点指定继承的父项目坐标，groupId、artifactId、version必选，唯一标识父项目，relativePath：可选，父项目的pom.xml文件的相对路径，默认值是../pom.xml.&lt;/p&gt; &lt;p&gt;Maven首先在当前构建项目的地方寻找父项目的pom，其次在本地文件系统的relativePath位置，然后在本地仓库，最后在远程仓库寻找父项目的pom.&lt;/p&gt; &lt;h4&gt;dependency依赖配置&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;xx&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;yy&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;x.x&amp;lt;/version&amp;gt; &amp;lt;scope&amp;gt; &amp;lt;/scope&amp;gt; &amp;lt;type&amp;gt;&amp;lt;/type&amp;gt; &amp;lt;optional&amp;gt;&amp;lt;/optional&amp;gt; &amp;lt;exclusions&amp;gt;   &amp;lt;exclusion&amp;gt;&amp;lt;/exclusion&amp;gt; &amp;lt;/exclusions&amp;gt; &amp;lt;/dependency&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;groupId、artifactId、version必选，依赖的基本坐标，找到需要的依赖。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;type&lt;/strong&gt;：依赖的类型，对应于项目坐标中定义的packaging，该元素不必声明默认为jar.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;scope&lt;/strong&gt;：依赖的范围&lt;/p&gt; &lt;p&gt;Maven在编译项目主代码时使用一套classpath，主代码中使用到的其他jar以依赖被引入到classpath中；Maven在编译和执行测试的时候会使用另外一套classpath；实际运行Maven项目的时候，又会使用一套classpath。故Maven项目依赖范围就是控制依赖于三种classpath（编译classpath、测试classpath、运行classpath）的关系。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Maven依赖范围有以下几种&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;compile&lt;/strong&gt;:编译依赖范围，没有指定默认使用该依赖范围。对于编译、测试、运行三种classpath都有效。例如：spring-core。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;test&lt;/strong&gt;:测试依赖范围，只对测试classpath有效，在编译主代码或者运行项目是无法使用。例如：junit。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;provided&lt;/strong&gt;:已提供依赖范围，对于编译和测试classpath有效，运行时无效。例如：servlet-api&lt;/p&gt; &lt;p&gt;&lt;strong&gt;runtime&lt;/strong&gt;:运行时依赖范围，对于测试和运行classpath有效，但在编译主代码时无效。例如：spring-jdbc。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;system&lt;/strong&gt;:系统依赖范围，和provided作用一致。但是system范围的依赖时必须通过systemPath元素显示第指定依赖文静的路径。由于该类依赖不是通过Maven仓库解析的，而且往往与本机系统绑定，可能造成不可抑制，应谨慎使用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;import&lt;/strong&gt;:只使用在dependencyManagement中，表示从其他的pom中导入depency的配置，不会对三种classpath产生实际的影响。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/q06qpajt4qhdjpgo5lpcs8q76t.png" alt="Maven的依赖范围" title="Maven的依赖范围" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;optional&lt;/strong&gt;:标记依赖是否可选&lt;/p&gt; &lt;p&gt;&lt;strong&gt;exclusions&lt;/strong&gt;：用来排除传递性依赖&lt;/p&gt; &lt;h4&gt;pluginManagement默认插件配置&lt;/h4&gt; &lt;p&gt;配置到project-build节点中，配置供子项目引用的插件。&lt;/p&gt; &lt;!--子项目可以引用的默认插件信息。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置--&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;pluginManagement&amp;gt;     &amp;lt;!--使用的插件列表--&amp;gt;     &amp;lt;plugins&amp;gt;         &amp;lt;!--plugin元素包含描述插件所需要的信息--&amp;gt;         &amp;lt;plugin&amp;gt;             &amp;lt;!--插件在仓库里的groupId--&amp;gt;             &amp;lt;groupId&amp;gt;&amp;lt;/groupId&amp;gt;             &amp;lt;!--插件在仓库里的artifactId--&amp;gt;             &amp;lt;artifactId&amp;gt;&amp;lt;/artifactId&amp;gt;             &amp;lt;!--被使用的插件的版本--&amp;gt;             &amp;lt;version&amp;gt;&amp;lt;/version&amp;gt;             &amp;lt;!--是否从该插件下载Maven扩展（例如打包和类型处理器）默认为false，由于性能原因，只有在真需要下载时，该元素才被设置成true。--&amp;gt;             &amp;lt;extensions&amp;gt;&amp;lt;/extensions&amp;gt;             &amp;lt;!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。--&amp;gt;             &amp;lt;executions&amp;gt;                 &amp;lt;!--execution元素包含了插件执行需要的信息--&amp;gt;                 &amp;lt;execution&amp;gt;                     &amp;lt;!--执行目标的标识符，用于标识构建过程中的目标，或者匹配继承过程中需要合并的执行目标--&amp;gt;                     &amp;lt;id&amp;gt;&amp;lt;/id&amp;gt;                     &amp;lt;!--绑定了目标的构建生命周期阶段，如果省略，目标会被绑定到源数据里配置的默认阶段--&amp;gt;                     &amp;lt;phase&amp;gt;&amp;lt;/phase&amp;gt;                     &amp;lt;!--配置的执行目标--&amp;gt;                     &amp;lt;goals&amp;gt;&amp;lt;/goals&amp;gt;                     &amp;lt;!--配置是否被传播到子POM--&amp;gt;                     &amp;lt;inherited&amp;gt;&amp;lt;/inherited&amp;gt;                     &amp;lt;!--作为DOM对象的配置--&amp;gt;                     &amp;lt;configuration&amp;gt;&amp;lt;/configuration&amp;gt;                 &amp;lt;/execution&amp;gt;             &amp;lt;/executions&amp;gt;             &amp;lt;!--项目引入插件所需要的额外依赖--&amp;gt;             &amp;lt;dependencies&amp;gt;                 &amp;lt;!--参见dependency元素配置--&amp;gt;                 &amp;lt;dependency&amp;gt;                 &amp;lt;/dependency&amp;gt;             &amp;lt;/dependencies&amp;gt;             &amp;lt;!--任何配置是否被传播到子项目--&amp;gt;             &amp;lt;inherited&amp;gt;&amp;lt;/inherited&amp;gt;             &amp;lt;!--作为DOM对象的配置--&amp;gt;             &amp;lt;configuration&amp;gt;&amp;lt;/configuration&amp;gt;         &amp;lt;/plugin&amp;gt;     &amp;lt;/plugins&amp;gt; &amp;lt;/pluginManagement&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;repositories远程仓库的配置&lt;/h4&gt; &lt;p&gt;很多情况默认的中央仓库无法满足项目需求，需要配置其他远程仓库，如JBoss Maven库，需要在Pom.xml文件中配置。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;repositories&amp;gt;  &amp;lt;repository&amp;gt;   &amp;lt;id&amp;gt;jboss-maven2-release-repository&amp;lt;/id&amp;gt;   &amp;lt;name&amp;gt;JBoss Repository&amp;lt;/name&amp;gt;   &amp;lt;url&amp;gt;http://repository.jboss.org/maven2/&amp;lt;/url&amp;gt;   &amp;lt;releases&amp;gt;    &amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;    &amp;lt;checksumPolicy&amp;gt;ignore&amp;lt;/checksumPolicy&amp;gt;    &amp;lt;updatePolicy&amp;gt;daily&amp;lt;/updatePolicy&amp;gt;   &amp;lt;/releases&amp;gt;   &amp;lt;snapshots&amp;gt;    &amp;lt;enabled&amp;gt;false&amp;lt;/enabled&amp;gt;    &amp;lt;checksumPolicy&amp;gt;fail&amp;lt;/checksumPolicy&amp;gt;    &amp;lt;updatePolicy&amp;gt;always&amp;lt;/updatePolicy&amp;gt;   &amp;lt;/snapshots&amp;gt;   &amp;lt;layout&amp;gt;default&amp;lt;/layout&amp;gt;  &amp;lt;/repository&amp;gt; &amp;lt;/repositories&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;在repositories元素下可以声明一个或多个远程仓库。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;id&lt;/strong&gt;:任何一个仓库声明的id必须是唯一的，Maven自带中央仓库id为central，如果其他仓库声明id也是central，就会覆盖中央仓库的配置。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;url&lt;/strong&gt;:指向仓库的地址，一般该地址是基于http协议，用户可以浏览器中打开仓库地址浏览构件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;releases&lt;/strong&gt;:支持发布版本下载。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;snapshots&lt;/strong&gt;:支持快照版本下载，enabled=true时开启releases和snapshots还有2个子元素。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;checksumPolicy&lt;/strong&gt;：配置Maven检查检验和文件的策略。当构建被部署到仓库中时，会同时部署对应的校验和文件，在下载构件的时候，Maven会验证校验和文件，如果校验和验证失败，会根据checksumPolicy的值进行选择。默认值是warn-Maven会在执行构建时输出警告信息，可用值还有fail-Maven遇到校验和错误时就让构建失败；ignore-使Maven完全忽略校验和错误。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;updatePolicy&lt;/strong&gt;：配置Maven从远程仓库检查更新的频率，默认值是daily。可用值有daily-每天检查一次、always-每次构建都会检查更新、interval:X-每个X分钟检查一次更新、never-从不检查更新。 layout:值为default表示仓库的布局是Maven2及Maven3的默认布局。&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 29 Oct 2017 16:18:58 GMT</pubDate>
    </item>
    <item>
      <title>一小时教你学会 Maven 项目的构建与管理(1)</title>
      <link>https://maruifu.cn/article/44</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Maven翻译成中文是“专家、内行”。Maven是Apache组织中一个颇为成功的开源项目，Maven主要服务于基于Java平台的项目构建、依赖管理和项目信息管理的优秀工具。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;img alt="xxx" src="" border="0" style="display:none;"/&gt; &lt;p&gt;Maven是优秀的构建工具：自动化构建过程、跨平台、标准化构建过程。&lt;/p&gt; &lt;p&gt;Maven为Java开发者提供了一个免费的中央仓库，其中几乎可以找到任何流行的开源类库，通过Maven的衍生工具Nexus，可以进行快速的搜索。Maven项目目录结构有约定的规则，约定优于配置（Convention Over Configuration）。&lt;/p&gt; &lt;p&gt;Ant（Another Neat Tool）另一个整洁的工具，Tomcat构建，过程式，开发者需要显式的指定每一个目标以及完成该目标所需要执行的任务，每一个项目都需要重新编写这一过程。&lt;/p&gt; &lt;p&gt;Maven是声明式的，项目构建过程和过程各阶段所需工作都要插件实现，大部分插件都是现成的，开发者只需要声明项目的基本元素，Maven就可以执行内置的，完整的构建过程。&lt;/p&gt; &lt;h3&gt;Maven 基础环境配置与基本命令&lt;/h3&gt; &lt;h4&gt;Maven的安装与配置分析&lt;/h4&gt; &lt;h5&gt;Maven环境的安装&lt;/h5&gt; &lt;p&gt;安装环境：Jdk1.7、Maven-3.5.0&lt;/p&gt; &lt;p&gt;第一步：JDK安装与配置。Windows和Linux下安装步骤可自行查找。&lt;/p&gt; &lt;p&gt;第二步：Maven的下载。官方下载地址：http://maven.apache.org/download.cgi&lt;/p&gt; &lt;p&gt;当前最新版本3.5.0，Windows上安装下载apache-maven-3.5.0-bin.zip&lt;/p&gt; &lt;p&gt;Linux上安装下载 apache-maven-3.5.0-bin.tar.gz&lt;/p&gt; &lt;h6&gt;Windows上安装Maven步骤：&lt;/h6&gt; &lt;p/&gt; 1.解压apache-maven-3.5.0-bin.zip到D:\develop\apache-maven-3.5.0 &lt;p/&gt; 2.配置环境变量 &lt;pre&gt;&lt;code&gt;    M2_HOME=D:\develop\apache-maven-3.5.0      Path末尾添加;% M2_HOME%\bin &lt;/code&gt;&lt;/pre&gt; &lt;p/&gt; 3.测试安装是否正确 &lt;p&gt;在命令行执行mvn –v，可以maven版本信息和基本配置信息表示配置成功。&lt;/p&gt; &lt;h6&gt;Linux上安装Maven步骤：&lt;/h6&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;解压 &lt;code&gt;tar xzvf apache-maven-3.5.0-bin.tar.gz&lt;/code&gt;&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;配置环境变量&lt;/p&gt; &lt;p&gt;export M2_HOME = /home/develop/apache-maven-3.5.0&lt;/p&gt; &lt;p&gt;export PATH = $PATH;$M2_HOME/bin&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;测试安装是否正确&lt;/p&gt; &lt;pre&gt;&lt;code&gt;  echo $M2_HOME 输出环境变量路径    mvn -v 输出版本号等相关信息 &lt;/code&gt;&lt;/pre&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h4&gt;安装目录分析&lt;/h4&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/7ovh8heg56iaor3uh8glscomsr.png" alt="Maven安装目录的图片" title="Maven安装目录的图片" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;bin&lt;/strong&gt;：该目录下mvn、mvnDebug是基于Linux平台的shell脚本，mvn.bat、mvnDebug.bat是基于Windows平台的bat脚本，在命令行输入mvn命令时实际上是调用mvn或mvn.bat脚本。mvn和mvnDebug的区别是mvnDebug多了一条MAVENDEBUGOPTS配置，作用是运行Maven时开启debug模式以调试Maven本身。m2.conf文件是classworlds的配置文件，Maven启动时会自动加载。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;boot&lt;/strong&gt;： 该目录只包含一个文件plexus-classworlds-2.5.2.jar，plexus-classworlds是一个类加载器框架，相对于默认的java类加载器，它提供了更加丰富的语法以便配置，Maven使用该框架加载自己的类库。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;conf&lt;/strong&gt;： 该目录包含了Maven的配置文件settings.xml，可以指定2种级别：全局级别：直接修改${maven.conf}/settings.xml文件可以全局定制Maven的行为，对一台机器上的所有用户有效。用户级别：将该文件复制到${user.home}/.m2/目录下，然后修改settings.xml配置，在当前用户范围内定制Maven的行为。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;lib&lt;/strong&gt;： 该目录包含了所有Maven运行时需要的Java类库，Maven本身是分模块的maven-*.jar都是maven自己的包，还有很多第三方依赖包。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;LICENSE&lt;/strong&gt;： Maven使用的软件许可证是Apache LicenseVersion 2.0。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;NOTICE&lt;/strong&gt;： Apache Maven Distribution使用的第三方软件。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;README.txt&lt;/strong&gt;： Maven的简明介绍，包括系统要求、安装说明、Maven URLS等。&lt;/p&gt; &lt;h3&gt;Maven的基本命令&lt;/h3&gt; &lt;p&gt;Maven项目构建过程中，主要构建命令有几种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;mvn validate 验证，验证项目是正确的并且所有的信息是可用的；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn clean 清理，清理项目缓存输出，一般是target文件夹被删除；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn compile 编译，将java源文件编译成.class文件；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn test 测试，生成测试报告，运行test目录下的所有单元测试；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn package 打包，将项目打成jar、war或者pom；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn install 安装，将当前项目安装到本地maven库，供其他项目依赖；&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;mvn deploy部署，在构建环境中完成，复制最终的包到远程库。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;执行后面的命令会自动执行前面的命令，比如执行mvn package时会执行validate、clean、compile、test、package五个阶段。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/pnrmpq34teh4ep2ibpp6hr7a9b.png" alt="mvn package的五个阶段的图片" title="mvn package的五个阶段的图片" /&gt;&lt;/p&gt; &lt;h2&gt;Maven 核心概念理论&lt;/h2&gt; &lt;h4&gt;Maven概念模型与依赖解析机制&lt;/h4&gt; &lt;p&gt;Maven根据项目的pom.xml文件，把它转化成项目对象模型(POM)，这时要解析依赖关系，然后去相对应的maven库中查找所依赖的jar包。在clean，compile，test，package等生命周期阶段都有相应的Plug-in来做这些事情，而这些Plug-in会产生一些中间产物。&lt;/p&gt; &lt;p&gt;Maven根据项目的pom.xml文件，把它转化成项目对象模型(POM)，这时要解析依赖关系，然后去相对应的maven库中查找所依赖的jar包。在clean，compile，test，package等生命周期阶段都有相应的Plug-in来做这些事情，而这些Plug-in会产生一些中间产物。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/38p14cl1dog3srl58phsp78fge.png" alt="Maven核心概念" title="Maven核心概念" /&gt;&lt;/p&gt; &lt;h4&gt;Maven从仓库解析依赖的机制&lt;/h4&gt; &lt;p&gt;当本地仓库没有依赖构件的时候，Maven会自动从远程仓库下载；当依赖版本为快照版本时，Maven会自动找到最新的快照。&lt;/p&gt; &lt;p&gt; 1.当依赖范围scope=system时，Maven直接从本地文件系统解析构件； &lt;p&gt; 2.根据依赖坐标计算仓库路径后，尝试直接从本地仓库寻找构件，若发现构件则解析成功； &lt;p&gt; 3.在本地仓库不存在相应构件的情况下，若依赖版本是显式的发布版本构件时，如1.1.0、1.2-alpha-1等，则便利所有的远程仓库，发现后下载到本地仓库并解析使用； &lt;p&gt; 4.如果依赖的版本是RELEASE或者LASTEST，则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml，将其与本地仓库的对应元数据合并后，计算出RELEASE或者LASTEST的真实值，然后基于真实值检查本地和远程仓库； &lt;p&gt; 5.如果依赖版本是SNAPSHOT，则基于更新策略读取所有远程仓库的元数据groupId/artifactId/version/maven-metadata.xml，将其与本地仓库的对应元数据合并后，得到最新快照版本的值，然后基于该值检查本地或者从远程仓库下载； &lt;p&gt; 6.如果最后解析到的构件版本是时间戳格式的快照，如1.0-20170712.191220-2，则复制其时间戳格式的文件至非时间戳格式，如SNAPSHOT，并使用该非时间戳格式的构件。 &lt;p&gt;当依赖的版本不明晰的时候，如RELEASE、LASTEST、SNAPSHOT，Maven就需要基于更新远程仓库的更新策略来检查更新。&lt;/p&gt; &lt;h4&gt;Maven仓库&lt;/h4&gt; &lt;p&gt;&lt;strong&gt;构件&lt;/strong&gt;：在Maven的世界，任何一个依赖、插件或者项目构建的输出，即xxx.jar;任何一个构件都有一组坐标唯一标识。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;仓库&lt;/strong&gt;：得益于坐标机制，任何Maven项目使用任何一个构件的方式都是完全相同的，在此基础上，Maven可以在某个位置统一存储所有Maven项目共享的构件，这个统一的位置就是仓库。&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt; public class DefaultRepositoryLayout     implements ArtifactRepositoryLayout {     private static final char PATH_SEPARATOR = '/';     private static final char GROUP_SEPARATOR = '.';     private static final char ARTIFACT_SEPARATOR = '-';     public String getId()     {         return &amp;quot;default&amp;quot;;     }      public String pathOf( Artifact artifact )     {         ArtifactHandler artifactHandler = artifact.getArtifactHandler();          StringBuilder path = new StringBuilder( 128 );          path.append( formatAsDirectory( artifact.getGroupId() ) ).append( PATH_SEPARATOR );         path.append( artifact.getArtifactId() ).append( PATH_SEPARATOR );         path.append( artifact.getBaseVersion() ).append( PATH_SEPARATOR );         path.append( artifact.getArtifactId() ).append( ARTIFACT_SEPARATOR ).append( artifact.getVersion() );          if ( artifact.hasClassifier() )         {             path.append( ARTIFACT_SEPARATOR ).append( artifact.getClassifier() );         }          if ( artifactHandler.getExtension() != null &amp;amp;&amp;amp; artifactHandler.getExtension().length() &amp;gt; 0 )         {             path.append( GROUP_SEPARATOR ).append( artifactHandler.getExtension() );         }          return path.toString();     }      private String formatAsDirectory( String directory )     {         return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );     } }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;例如：groupId=com.feiyue、artifactId=demo、version=1.0、artifactId=jdk7、packaging=jar 其对应的路径生成如下：&lt;/p&gt; &lt;p/&gt; 1.groupId路径：formatAsDirectory()将groupId中的'.'转换成'/'，com.feiyue就会转换成com/feiyue，之后再加一个'/'，就变成com/feiyue/ &lt;p/&gt; 2.artifactId路径：在groupId基础的加上artifactId，再加上一个'/，变成com/feiyue/demo/ &lt;p/&gt; 3.version路径：在前面基础上加上version，再加上一个'/，变成com/feiyue/demo/1.0/ &lt;p/&gt; 4.依次加上artifactId、一个'-’、version，就变成com/feiyue/demo/1.0/demo-1.0 &lt;p/&gt; 5.如果有classfier,4)会变成com/feiyue/demo/1.0/demo-1.0-jdk7 &lt;p/&gt; 6.如果extension存在则依次加上'.’、extension。代码中extension是从artifactHandler而非artifact中获取，artifactHandler是由packaging决定的。故packaging决定了构件的扩展名，因此最终的路径为com/feiyue/demo/1.0/demo-1.0-jdk7.jar &lt;h5&gt;Maven仓库的分类&lt;/h5&gt; &lt;p&gt;Maven仓库分为两类：本地仓库和远程仓库。当Maven根据坐标寻找构件时，首先会查看本地仓库，若本地仓库存在此构件则直接使用；若本地仓库不存在此构件，Maven就会去远程仓库查找，查找到下载到本地仓库再使用。若本地仓库和远程仓库都没有需要的构件，Maven就会报错。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;中央仓库&lt;/strong&gt;： Maven核心自带的远程仓库，包含了绝大部分开源构件，默认情况，当本地仓库没有Maven需要构件时，就从中央仓库下载。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;私服&lt;/strong&gt;：一种特殊的远程仓库，为节省带宽和时间，应在局域网内架设一个私有仓库服务器，用其代理所有外部的远程仓库。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;本地仓库&lt;/strong&gt;：用户自定义本地仓库的地址，需编辑${user.home}/.m2/setting.xml文件，设置localRepository节点的值为仓库地址即可，默认情况下${user.home}/.m2/setting.xml是不存在的，需要用户从安装目录复制${M2_HOME}/conf/setting.xml文件在进行编辑。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;settings&amp;gt; &amp;lt;localRepository&amp;gt;E:/maven/repository&amp;lt;/localRepository&amp;gt; &amp;lt;/settings&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;strong&gt;中央仓库&lt;/strong&gt;： Maven默认的远程仓库，安装文件中自带了中央仓库的配置，在${M2_HOME}/lib/maven-model-builder-3.2.5.jar中，解压缩找到org\apache\maven\model\pom-4.0.0.xml,可以看到如下默认远程仓库配置：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;repositories&amp;gt;   &amp;lt;repository&amp;gt;    &amp;lt;id&amp;gt;central&amp;lt;/id&amp;gt;    &amp;lt;name&amp;gt;Central Repository&amp;lt;/name&amp;gt;    &amp;lt;url&amp;gt;https://repo.maven.apache.org/maven2&amp;lt;/url&amp;gt;    &amp;lt;layout&amp;gt;default&amp;lt;/layout&amp;gt;    &amp;lt;snapshots&amp;gt;        &amp;lt;enabled&amp;gt;false&amp;lt;/enabled&amp;gt;    &amp;lt;/snapshots&amp;gt;   &amp;lt;/repository&amp;gt; &amp;lt;/repositories&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个配置文件是所有Maven项目都会继承的超级POM.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;私服&lt;/strong&gt;：特殊的远程仓库，架设在局域网内的仓库服务，代理公网的远程仓库，当Maven需要下载构件时，从私服请求，若私服不存在该构件，则从公网远程仓库下载，缓存到私服之后，再为Maven的下载请求提供服务。另外无法从公网仓库下载的构件也能从本地上传到私服供项目使用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;私服优点&lt;/strong&gt;：节省外网带宽、提供Maven构件速度、部署第三方构件、提供Maven构件稳定性、降低中央仓库负荷。&lt;/p&gt; &lt;h5&gt;Maven坐标&lt;/h5&gt; &lt;p&gt;唯一标识Maven构件，坐标元素分为groupId、artifactId、version、packaging、classifier.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;groupId&lt;/strong&gt;:必选，定义当前Maven项目隶属的实际项目，不一定是一对一的关系，通常一个实际项目会被划分成很多模块。groupId一般不应该只定义到公司级别，一个公司可能会有很多实际项目，如果groupId只定义到组织级别，那么artifactId只能定义Maven项目。命名方式和Java包名类似，域名反向一一对应。例如：org.springframework.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;artifactId&lt;/strong&gt;:必选，定义实际项目中的一个Maven模块，推荐使用实际项目名称-模块名称，这样便于找到某个项目的一组构件。例如：spring-core,spring-beans,spring-web等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;version&lt;/strong&gt;:必选，定义Maven项目当前所处的版本。例如：4.3.9.RELEASE、1.0-SNAPSHOT、RELEASE、LATEST、2.1等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;packaging&lt;/strong&gt;:可选默认是jar，定义Maven项目的打包方式。打包方式有jar、war、pom等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;classifier&lt;/strong&gt;:不能直接定义，帮助定义构建输出的一些附属构件。附属构件与主构件对应，例如-javadoc.jar、-sources.jar附属构件包含了java文档和源代码。&lt;/p&gt; &lt;h3&gt;依赖管理&lt;/h3&gt; &lt;p&gt;依赖管理分为传递性依赖、依赖调解、可选依赖、排除依赖、归类依赖等。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;传递性依赖&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/r63bjitg30jc6p52m23kib0ufa.png" alt="Maven传递性依赖的图片" title="Maven传递性依赖的图片" /&gt;&lt;/p&gt; &lt;p&gt;maven模块 -&amp;gt; spring-jdbc -&amp;gt; spring-core -&amp;gt; commons-logging&lt;/p&gt; &lt;p&gt;假设： A -&amp;gt; B -&amp;gt; C，即A对B是第一直接依赖，B对C是第二直接依赖，A对C是传递性依赖，第一直接依赖（简称F）和第二直接依赖（简称S）的范围决定了传递性依赖（简称T）的范围。如图所示：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/2caie1vnf6ib3rf4m9lqp4dbu4.png" alt="Maven传递性依赖2" title="Maven传递性依赖2" /&gt;&lt;/p&gt; &lt;p&gt;例如：A -&amp;gt; B -&amp;gt; C,A依赖B的范围是test，B依赖C的范围是compile，则A传递依赖C的范围是test。&lt;/p&gt; &lt;p&gt;结论：当S=compile时，T与F的范围一致；当S=test时，依赖不会传递；当S=provided时，只有当F=provided时，T=provided；当S=runtime时，T=F，但F=compile例外，此时T=runtime.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;依赖调解&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;依赖调解第一原则：路径最近者优先。&lt;/p&gt; &lt;p&gt;例如：A -&amp;gt; B -&amp;gt; C -&amp;gt; X1 长度为3 A -&amp;gt; D -&amp;gt; X2 长度为2，因此X2会被解析使用 依赖调解第二原则：第一原则优先，依赖路径相等时，POM中依赖声明顺序靠前的优先。&lt;/p&gt; &lt;p&gt;例如：A -&amp;gt; B -&amp;gt; X1 长度为2 A -&amp;gt; C -&amp;gt; X2 长度为2，但是POM文件中B的依赖声明靠前，因此X1会被解析使用。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;可选依赖&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;A依赖于B，B依赖于X和Y，B对于X和Y的依赖都是可选依赖，即optional=true&lt;/p&gt; &lt;p&gt;AB、BX（可选）、BY（可选）。可选依赖不会传递，即X、Y对A没有影响。&lt;/p&gt; &lt;p&gt;可选依赖一般是多种互斥的特性，具体使用时只选其一。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/75ddohu6h4hbarcdftrtlc785h.png" alt="可选依赖的图片" title="可选依赖的图片" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;排除依赖&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;使用exclusions元素声明排除依赖，exclusions包含一个或者多个exclusion子元素，因此可以排除一个或者多个传递性依赖。注意声明exclusion时只需要groupId和artifactId，而不需要version元素，因为只需要groupId和artifactId就可以唯一定位依赖图中的某个依赖。&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/u3f9fmd2jqi66p0e6ksntja7m3.png" alt="排除依赖" title="排除依赖" /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;归类依赖&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;spring的依赖包版本都是相同的，可以使用properties元素定义Maven属性spring.version=4.x&lt;/p&gt; &lt;p&gt;在定义依赖时可以使用美元符号加大括弧环绕的方式来引用Maven属性，例如${spring.version}。&lt;/p&gt; &lt;h3&gt;聚合与继承&lt;/h3&gt; &lt;p&gt;聚合：多个项目或者模块聚合到一起，建立一个package方式为pom的项目parent专门负责聚合工作，并使用modules-module指定子模块，目的是快速构建项目。&lt;/p&gt; &lt;p&gt;继承：多个模块聚合时，子模块需要继承父模块以消除重复配置。&lt;/p&gt; &lt;p&gt;聚合与继承的共同点是聚合POM与继承关系中的父POM的packaging都必须是pom。&lt;/p&gt; &lt;p&gt;聚合关系与继承关系的比较如下图所示：&lt;/p&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/4dsnhdb70eguhqlfb363knrr0k.png" alt="聚合与继承" title="聚合与继承" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Sun, 29 Oct 2017 15:23:34 GMT</pubDate>
    </item>
    <item>
      <title>Markdown 快速生成表格</title>
      <link>https://maruifu.cn/article/43</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;在Markdown上写一个表格真是让人头疼的事情，写的不流畅还要担心格式。我为大家总结了以下三种方法，前两种大家或许司空见惯了，第三种是神器。。。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;一、md原生&lt;/p&gt; &lt;pre&gt;&lt;code&gt;| 水果        | 价格    |  数量  | | --------   | -----:   | :----: | | 香蕉        | $1      |   5    | | 苹果        | $1      |   6    | | 草莓        | $1      |   7    | &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这种写法出来效果就是如下:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th&gt;水果&lt;/th&gt;&lt;th align="right"&gt;价格&lt;/th&gt;&lt;th align="center"&gt;数量&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td&gt;香蕉&lt;/td&gt;&lt;td align="right"&gt;$1&lt;/td&gt;&lt;td align="center"&gt;5&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;苹果&lt;/td&gt;&lt;td align="right"&gt;$1&lt;/td&gt;&lt;td align="center"&gt;6&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;草莓&lt;/td&gt;&lt;td align="right"&gt;$1&lt;/td&gt;&lt;td align="center"&gt;7&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;二、html表格&lt;/p&gt; &lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;table&amp;gt;         &amp;lt;tr&amp;gt;             &amp;lt;th&amp;gt;设备&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;设备文件名&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;文件描述符&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;类型&amp;lt;/th&amp;gt;         &amp;lt;/tr&amp;gt;         &amp;lt;tr&amp;gt;             &amp;lt;th&amp;gt;键盘&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;/dev/stdin&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;0&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;标准输入&amp;lt;/th&amp;gt;         &amp;lt;/tr&amp;gt;         &amp;lt;tr&amp;gt;             &amp;lt;th&amp;gt;显示器&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;/dev/stdout&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;1&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;标准输出&amp;lt;/th&amp;gt;         &amp;lt;/tr&amp;gt;         &amp;lt;tr&amp;gt;             &amp;lt;th&amp;gt;显示器&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;/dev/stderr&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;2&amp;lt;/th&amp;gt;             &amp;lt;th&amp;gt;标准错误输出&amp;lt;/th&amp;gt;         &amp;lt;/tr&amp;gt;     &amp;lt;/table&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这种写法出来效果就是如下:&lt;/p&gt; &lt;table&gt;         &lt;tr&gt;             &lt;th&gt;设备&lt;/th&gt;             &lt;th&gt;设备文件名&lt;/th&gt;             &lt;th&gt;文件描述符&lt;/th&gt;             &lt;th&gt;类型&lt;/th&gt;         &lt;/tr&gt;         &lt;tr&gt;             &lt;th&gt;键盘&lt;/th&gt;             &lt;th&gt;/dev/stdin&lt;/th&gt;             &lt;th&gt;0&lt;/th&gt;             &lt;th&gt;标准输入&lt;/th&gt;         &lt;/tr&gt;         &lt;tr&gt;             &lt;th&gt;显示器&lt;/th&gt;             &lt;th&gt;/dev/stdout&lt;/th&gt;             &lt;th&gt;1&lt;/th&gt;             &lt;th&gt;标准输出&lt;/th&gt;         &lt;/tr&gt;         &lt;tr&gt;             &lt;th&gt;显示器&lt;/th&gt;             &lt;th&gt;/dev/stderr&lt;/th&gt;             &lt;th&gt;2&lt;/th&gt;             &lt;th&gt;标准错误输出&lt;/th&gt;         &lt;/tr&gt;     &lt;/table&gt; &lt;p&gt;三、excel表格&lt;/p&gt; &lt;p&gt;这个可以说是大杀器了，我们只需要下载一个东西就行了，这个是知乎用户幻灰龙写的东西，亲测有效&lt;/p&gt; &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;a href="https://img.maruifu.com/images/blog/2017/10/3m5rtcca58is0p5ci9igld9d1r.exe" target="_blank"&gt;下载链接&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt; &lt;p&gt;解压下来就能把excel变成md表格了&lt;/p&gt; &lt;p&gt;在解压目录下，使用以下命令行，把xx的部分换成表格名称就行了（注意路径问题），windows就行了，不需要linux&lt;/p&gt; &lt;p&gt;exceltk用例 整个表格：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;             exceltk.exe -t md -xls xxx.xls                exceltk.exe -t md -xls xxx.xlsx  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;指定sheet：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;             exceltk.exe -t md -xls xx.xls -sheet sheetname                 exceltk.exe -t md -xls xx.xlsx -sheet sheetnameexceltk &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;特性：&lt;/p&gt; &lt;P/&gt; ●      转换Excel表格到MarkDown表格 &lt;P/&gt; ●      支持Excel单元格带超链接 &lt;P/&gt; ●       如果Excel里有合并的跨行单元格，在转换后的MarkDown里是分开的单元格，这是因为MarkDown本身不支持跨行单元格 &lt;P/&gt; ●      如果Excel表格右侧有大量的空列，则会被自动裁剪，算法是根据前100行来检测并计算</content:encoded>
      <pubDate>Fri, 27 Oct 2017 02:13:50 GMT</pubDate>
    </item>
    <item>
      <title>Java 8教程</title>
      <link>https://maruifu.cn/article/42</link>
      <content:encoded>&lt;h3&gt;Java 8 - 简介&lt;/h3&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Java 8于2014年初发布。在java 8中，大多数关于功能的是lambda表达式。它还有许多其他重要功能，如默认方法，Streams API和新的日期/时间API。让我们在java 8中了解这些新功能的例子。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;img alt="xxx" src="https://img.maruifu.com/images/blog/2017/10/q35qfr5p5gj1fpr0v84vm5r8an.jpg" border="0" style="display:none;"/&gt; &lt;h4&gt;Lambda表达&lt;/h4&gt; &lt;p&gt;我们许多已经使用高级语言（如Scala）的人们并不知道Lambda表达式。在编程中，Lambda表达式（或函数）只是一个匿名函数，即一个没有名称而不被绑定到一个标识符的函数。它们被完全写在需要的地方，通常作为其他功能的参数。&lt;/p&gt; &lt;p&gt;&lt;em&gt;lambda表达式的基本语法是：&lt;/em&gt;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;either (parameters) -&amp;gt; expression or (parameters) -&amp;gt; { statements; } or () -&amp;gt; expression &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;典型的lambda表达式示例将如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(x, y) -&amp;gt; x + y  //This function takes two parameters and return their sum. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;请注意，根据x和y的类型，方法可能会在多个地方使用。参数可以匹配int，或整数或简单的字符串。基于上下文，它将添加两个整数或两个字符串。&lt;/p&gt; &lt;h4&gt;编写lambda表达式的规则&lt;/h4&gt; &lt;p/&gt;1. lambda表达式可以具有零个，一个或多个参数。 &lt;p/&gt;2. 可以显式声明参数的类型，也可以从上下文推断参数的类型。 &lt;p/&gt;3. 多个参数用强制括号括起来，用逗号分隔。空括号用于表示一组空的参数。 &lt;p/&gt;4. 当有一个参数时，如果推断出它的类型，则不必使用括号。例如a - &gt; return a * a。 &lt;p/&gt;5. lambda表达式的主体可以包含零个，一个或多个语句。 &lt;p/&gt;6. 如果lambda表达式的主体具有单个语句，则大括号不是强制性的，匿名函数的返回类型与body表达式的返回类型相同。当身体中有多于一个的声明必须用大括号括起来。 &lt;p&gt;阅读更多：Java 8 Lambda表达式教程&lt;/p&gt; &lt;h4&gt;函数式接口&lt;/h4&gt; &lt;p&gt;函数式接口也称为单抽象方法接口（SAM接口）。正如名字所暗示的，他们只允许一个抽象方法。Java 8引入了一个注释，即@FunctionalInterface当您注释的界面违反了函数式接口的合同时，可以用于编译器级错误。&lt;/p&gt; &lt;p&gt;典型的函数式接口示例：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@FunctionalInterface public interface MyFirstFunctionalInterface {  public void firstWork(); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;请注意，即使@FunctionalInterface省略注释，函数式接口也是有效的。它仅用于通知编译器在界面内强制执行单个抽象方法。&lt;/p&gt; &lt;p&gt;此外，由于默认方法不是抽象的，您可以随意添加默认方法到您的函数式接口尽可能多的你喜欢。&lt;/p&gt; &lt;p&gt;要记住的另一个重要的一点是，如果一个接口声明一个覆盖其中一个公共方法的抽象方法java.lang.Object，也不会计入接口的抽象方法计数，因为接口的任何实现都将具有来自java.lang.Object或其他地方的实现。例如，下面是完全有效的函数式接口。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@FunctionalInterface public interface MyFirstFunctionalInterface  {  public void firstWork();   @Override  public String toString();//Overridden from Object class   @Override  public boolean equals(Object obj);//Overridden from Object class } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;阅读更多：Java 8函数式接口教程&lt;/p&gt; &lt;h4&gt;默认方法&lt;/h4&gt; &lt;p&gt;Java 8允许您在接口中添加非抽象方法。这些方法必须被声明为默认方法。java 8中引入了默认方法来启用lambda表达式的功能。&lt;/p&gt; &lt;p&gt;默认方法使您能够向库的接口添加新功能，并确保与旧版本的这些接口编写的代码的二进制兼容性。&lt;/p&gt; &lt;p&gt;我们来看一个例子：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public interface Moveable {     default void move(){         System.out.println(&amp;quot;I am moving&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Moveable接口定义了一个方法，move()并提供了一个默认的实现。如果任何类实现了这个接口，那么它不需要实现它自己的move()方法版本。它可以直接打电话instance.move()。例如&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Animal implements Moveable{     public static void main(String[] args){         Animal tiger = new Animal();         tiger.move();     } }   Output: I am moving &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果类愿意定制move()方法的行为，那么它可以提供它自己的自定义实现并覆盖该方法。&lt;/p&gt; &lt;p&gt;Reda更多：Java 8默认方法教程&lt;/p&gt; &lt;h4&gt;Streams&lt;/h4&gt; &lt;p&gt;另一个重大改变引入了Java 8 Streams API，它提供了一种以各种方式处理一组数据的机制，可以包括过滤，转换或可能对应用程序有用的任何其他方式。&lt;/p&gt; &lt;p&gt;Java 8中的Streams API支持一种不同类型的迭代，其中您只需定义要处理的项目集合，对每个项目执行的操作以及要存储这些操作的输出的位置。&lt;/p&gt; &lt;p&gt;StreamsAPI的一个例子。在此示例中，items是String值的集合，并且要删除以某些前缀文本开头的条目。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; items; String prefix; List&amp;lt;String&amp;gt; filteredList = items.stream().filter(e -&amp;gt; (!e.startsWith(prefix))).collect(Collectors.toList()); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里items.stream()表示我们希望items使用Streams API处理集合中的数据。&lt;/p&gt; &lt;p&gt;阅读更多：Java 8内部与外部迭代&lt;/p&gt; &lt;h4&gt;日期/时间API更改&lt;/h4&gt; &lt;p&gt;新的日期和时间API /类（JSR-310）也称为ThreeTen，它们简单地改变了在java应用程序中处理日期的方式。&lt;/p&gt; &lt;h4&gt;日期&lt;/h4&gt; &lt;p&gt;日期类甚至已经过时了。旨在取代Date类新的类LocalDate，LocalTime和LocalDateTime。&lt;/p&gt; &lt;p/&gt; 1. 本LocalDate类代表一个日期。没有时间或时区的表示。 &lt;p/&gt; 2. 这个LocalTime班代表一个时间。没有表示日期或时区。 &lt;p/&gt; 3. 本LocalDateTime类代表一个日期-时间。没有时区的表示。 &lt;p/&gt; 4. 如果您希望使用区域信息的日期功能，那么拉姆达为您提供额外的3类类似于一个，即上面OffsetDate，OffsetTime和OffsetDateTime。时区偏移可以用“+05：30”或“欧洲/巴黎”格式表示。这是通过使用另一个类来完成的ZoneId。 &lt;pre&gt;&lt;code&gt;LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.of(12, 20); LocalDateTime localDateTime = LocalDateTime.now();  OffsetDateTime offsetDateTime = OffsetDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(&amp;quot;Europe/Paris&amp;quot;)); &lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;时间戳和持续时间&lt;/h4&gt; &lt;p&gt;为了表示任何时刻的具体时间戳，需要使用的类是Instant。本Instant类表示时间纳秒的精度瞬间。Instant上的操作包括与另一个进行比较，Instant并添加或减少持续时间。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Instant instant = Instant.now(); Instant instant1 = instant.plus(Duration.ofMillis(5000)); Instant instant2 = instant.minus(Duration.ofMillis(5000)); Instant instant3 = instant.minusSeconds(10); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Durationclass是Java语言第一次带来的全新概念。它代表两个时间戳之间的时差。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Duration duration = Duration.ofMillis(5000); duration = Duration.ofSeconds(60); duration = Duration.ofMinutes(10); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Duration处理小时间单位，例如毫秒，秒，分钟和小时。它们更适合与应用程序代码交互。要与人交互，你需要获得更多的持续时间，这是与Period课堂呈现的。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Period period = Period.ofDays(6); period = Period.ofMonths(6); period = Period.between(LocalDate.now(), LocalDate.now().plusDays(60)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;阅读更多：Java 8日期和时间API更改&lt;/p&gt; &lt;h2&gt;Java 8 Lambda表达式教程&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;一个非常全新而令人兴奋的功能，java 8搭配它，是Lambda表达式。我们许多已经从事高级语言（如scala）工作的人们并不为人所知。事实上，如果你看历史，并尝试在过去二十年里发现java中的任何语言改进，你将无法回想起许多令人兴奋的事情。在过去十年中，只有少数并发类，泛型，如果您同意注释，在java中是显着的增加。Lambda表达式打破了这场干旱，感觉像一个愉快的礼物。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h3&gt;Java中的lambda表达式是什么？&lt;/h3&gt; &lt;p&gt;在编程中，Lambda表达式（或函数）只是一个匿名函数，即一个没有名称而不被绑定到一个标识符的函数。&lt;/p&gt; &lt;p&gt;换句话说，lambda表达式是作为常量值给出的无名函数，并且准确地写在需要的地方，通常作为一些其他函数的参数。&lt;/p&gt; &lt;p&gt;Lambda表达式最重要的特征是它们在其外观的上下文中执行。所以，类似的lambda表达式可以在某些其他上下文中执行不同的方式（即逻辑将是相同的，但结果会根据传递给函数的不同参数而不同）。 以上定义充满了关键字，只有当您已经深入了解什么是lambda表达式才能被理解。所以，一旦你在下一节更好地了解了lambda表达式，我建议你重新阅读上面的段落。&lt;/p&gt; &lt;p&gt;所以，很明显，lambda是没有名称和标识符的某种功能。那么什么大事呢？为什么大家都很兴奋？&lt;/p&gt; &lt;p&gt;答案在于面向对象编程（OOP）的功能编程所带来的好处。大多数OOP语言围绕对象和实例进行演化，仅对待他们的一Streams公民。另一个重要的实体即功能占据了位置。这在java中尤其如此，其中函数不能存在于对象之外。一个函数本身并不意味着java中的任何东西，直到它与某个对象或实例相关。&lt;/p&gt; &lt;p&gt;但是在函数式编程中，您可以定义函数，给它们引用变量，并将它们作为方法参数传递。JavaScript是一个很好的例子，您可以将回调方法传递给Ajax调用。这是非常有用的功能，一开始就缺少java。现在用java 8，我们也可以使用这些lambda表达式。&lt;/p&gt; &lt;p&gt;典型的lambda表达式语法将如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(x, y) -&amp;gt; x + y  //This function takes two parameters //and return their sum. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在基于x和y的类型，方法可以在多个地方使用。参数可以匹配int，或整数或简单的字符串。基于上下文，它将添加两个整数或两个字符串。&lt;/p&gt; &lt;p&gt;句法：&lt;/p&gt; &lt;p&gt;lambda表达式的基本语法是&lt;/p&gt; &lt;pre&gt;&lt;code&gt;either (parameters) -&amp;gt; expression or (parameters) -&amp;gt; { statements; } or () -&amp;gt; expression &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;让我们看一些例子：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(int a, int b) -&amp;gt;a * b   // takes two integers and returns their multiplication (a, b)  -&amp;gt;   a - b   // takes two numbers and returns their difference () -&amp;gt; 99 // takes no values and returns 99 (String a) -&amp;gt; System.out.println(a)  // takes a string, prints its value to the console, and returns nothing a -&amp;gt; 2 * a   // takes a number and returns the result of doubling it c -&amp;gt; { //some complex statements }   // takes a collection and do some procesing &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们来确定一些可以帮助我们编写lambda表达式的规则：&lt;/p&gt; &lt;P/&gt; ● lambda表达式可以具有零个，一个或多个参数。 &lt;P/&gt; ● 可以显式声明参数的类型，也可以从上下文推断参数的类型。 &lt;P/&gt; ● 多个参数用强制括号括起来，用逗号分隔。空括号用于表示一组空的参数。 &lt;P/&gt; ● 当有一个参数时，如果推断出它的类型，则不必使用括号。例如a - &gt; return a * a。 &lt;P/&gt; ● lambda表达式的主体可以包含零个，一个或多个语句。 &lt;P/&gt; ● 如果lambda表达式的主体具有单个语句，则大括号不是强制性的，匿名函数的返回类型与body表达式的返回类型相同。当身体中有多于一个的声明必须用大括号括起来。 &lt;p&gt;所以，我们简要概述了什么是lambda表达式。如果您感到遗失并且无法关联，请耐心等待，如何在Java编程语言中使用。我们将在接下来的30分钟内做出一切。所以我们来吧&lt;/p&gt; &lt;p&gt;在深入讨论lambda表达式和java编程之前，您还必须了解功能接口。这太重要了&lt;/p&gt; &lt;h3&gt;什么是函数式接口？&lt;/h3&gt; &lt;p&gt;单抽象方法接口（SAM接口）不是一个新概念。这意味着只有一种方法的接口。在java中，我们已经有了很多这样的SAM接口的例子。从java 8，他们也将被称为功能接口。Java 8通过使用新的注释（即@FunctionalInterface）标记这些接口来强制执行单一责任规则。&lt;/p&gt; &lt;p&gt;例如，Runnable界面的新定义如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;@FunctionalInterface public interface Runnable { public abstract void run(); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果您尝试在任何函数式接口中添加新方法，编译器将不允许您执行此操作，并将抛出编译时错误。&lt;/p&gt; &lt;p&gt;到现在为止还挺好。但是，它们如何与Lambda表达式相关？让我们找出答案。&lt;/p&gt; &lt;p&gt;我们知道Lambda表达式是没有名称的匿名函数，它们（主要）被传递给其他函数作为参数。那么在java方法中，参数总是有一个类型，并且这种类型的信息被查找以确定在方法重载或甚至简单的方法调用的情况下需要调用哪个方法。因此，基本上每个lambda表达式也必须可转换为某些类型才能被接受为方法参数。那么lambda表达式转换的类型总是功能接口类型。&lt;/p&gt; &lt;p&gt;让我们以一个例子来理解它。如果我们要编写一个在控制台中打印“howtodoinjava”的线程，那么最简单的代码将是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;new Thread(new Runnable() { @Override public void run() { System.out.println(&amp;quot;howtodoinjava&amp;quot;); } }).start(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果我们使用lambda表达式来执行此任务，那么代码将是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;new Thread( () -&amp;gt;   { System.out.println(&amp;quot;My Runnable&amp;quot;); }  ).start(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们还看到，Runnable是一个函数式接口，具有单一方法run（）。因此，当您将lambda表达式传递给Thread类的构造函数时，编译器将尝试将表达式转换为等效的Runnable代码，如第一个代码示例所示。如果编译器成功，那么一切运行正常，如果编译器无法将表达式转换为等效的实现代码，则会抱怨。这里，在上面的例子中，lambda表达式被转换为Runnable类型。&lt;/p&gt; &lt;p&gt;简单来说，lambda表达式是函数式接口的一个实例。但是，lambda表达式本身并不包含其实现的功能接口的信息; 该信息是从其使用的上下文推导出来的。&lt;/p&gt; &lt;h3&gt;几个Lambda表达式的例子&lt;/h3&gt; &lt;p&gt;我列出了一些代码示例，您可以阅读和分析如何在日常编程中使用lambda表达式。&lt;/p&gt; &lt;p&gt;1）迭代列表并执行一些操作&lt;/p&gt; &lt;p&gt;List&lt;String&gt; pointList = new ArrayList(); pointList.add(&amp;quot;1&amp;quot;); pointList.add(&amp;quot;2&amp;quot;);&lt;/p&gt; &lt;pre&gt;&lt;code&gt;pointList.forEach(p -&amp;gt;  { System.out.println(p); //Do more work }  ); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2）创建一个新的runnable并传递给线程&lt;/p&gt; &lt;pre&gt;&lt;code&gt;new Thread( () -&amp;gt; System.out.println(&amp;quot;My Runnable&amp;quot;); ).start(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;3）按照他们的名字对雇员对象进行排序&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class LambdaIntroduction {     public static void main (String[] ar){   Employee[] employees  = {   new Employee(&amp;quot;David&amp;quot;),   new Employee(&amp;quot;Naveen&amp;quot;),   new Employee(&amp;quot;Alex&amp;quot;),   new Employee(&amp;quot;Richard&amp;quot;)};    System.out.println(&amp;quot;Before Sorting Names: &amp;quot;+Arrays.toString(employees));   Arrays.sort(employees, Employee::nameCompare);   System.out.println(&amp;quot;After Sorting Names &amp;quot;+Arrays.toString(employees));   } }    class Employee {   String name;      Employee(String name) { this.name = name;   }      public static int nameCompare(Employee a1, Employee a2) { return a1.name.compareTo(a2.name);   }    public String toString() { return name;   } }   Output:   Before Sorting Names: [David, Naveen, Alex, Richard] After Sorting Names [Alex, David, Naveen, Richard] &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4）将事件侦听器添加到GUI组件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;JButton button =  new JButton(&amp;quot;Submit&amp;quot;); button.addActionListener((e) -&amp;gt; { System.out.println(&amp;quot;Click event triggered !!&amp;quot;); });           &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上是java 8中的lambda表达式的非常基本的例子。我将不时提出更有用的示例和代码示例。&lt;/p&gt; &lt;h2&gt;Java 8方法引用与示例&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;在Java 8中，您可以使用class::methodName类型语法引用类或对象的方法。让我们在java 8中了解不同类型的可用方法引用。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h3&gt;方法参考的类型 - 快速概述&lt;/h3&gt; &lt;p&gt;Java 8有四种类型的方法引用。&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="left"&gt;方法参考&lt;/th&gt;&lt;th align="left"&gt;描述&lt;/th&gt;&lt;th align="left"&gt;例&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="left"&gt;参考静态方法&lt;/td&gt;&lt;td align="left"&gt;用于引用类中的静态方法&lt;/td&gt;&lt;td align="left"&gt;Math::max 相当于 Math.max(x,y)&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;从实例引用instance方法&lt;/td&gt;&lt;td align="left"&gt;请参考实例方法使用提供的对象的引用&lt;/td&gt;&lt;td align="left"&gt;System.out::println 相当于System.out.println(x)&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;从类类型引用instance方法&lt;/td&gt;&lt;td align="left"&gt;在对上下文提供的对象的引用上调用实例方法&lt;/td&gt;&lt;td align="left"&gt;String::length 相当于str.length()&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="left"&gt;参考构造函数&lt;/td&gt;&lt;td align="left"&gt;引用构造函数&lt;/td&gt;&lt;td align="left"&gt;ArrayList::new 相当于 new ArrayList()&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3&gt;引用静态方法 - Class :: staticMethodName&lt;/h3&gt; &lt;p&gt;一个使用Math.max()静态方法的例子。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; integers = Arrays.asList(1,12,433,5);   Optional&amp;lt;Integer&amp;gt; max = integers.stream().reduce( Math::max );   max.ifPresent(value -&amp;gt; System.out.println(value)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;433 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;引用实例方法从实例 - ClassInstance :: instanceMethodName&lt;/h3&gt; &lt;p&gt;在上面的例子中，我们System.out.println(value)用来打印找到的最大值。我们可以System.out::println用来打印值。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; integers = Arrays.asList(1,12,433,5);   Optional&amp;lt;Integer&amp;gt; max = integers.stream().reduce( Math::max );   max.ifPresent( System.out::println ); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;433 &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;引用类类型的实例方法 - Class :: instanceMethodName&lt;/h3&gt; &lt;p&gt;在这个例子中，s1.compareTo（s2）被称为String::compareTo。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;List&amp;lt;String&amp;gt; strings = Arrays .asList(&amp;quot;how&amp;quot;, &amp;quot;to&amp;quot;, &amp;quot;do&amp;quot;, &amp;quot;in&amp;quot;, &amp;quot;java&amp;quot;, &amp;quot;dot&amp;quot;, &amp;quot;com&amp;quot;);   List&amp;lt;String&amp;gt; sorted = strings .stream() .sorted((s1, s2) -&amp;gt; s1.compareTo(s2)) .collect(Collectors.toList());   System.out.println(sorted);   List&amp;lt;String&amp;gt; sortedAlt = strings .stream() .sorted(String::compareTo) .collect(Collectors.toList());   System.out.println(sortedAlt); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;[com，do，dot，how，in，java，to] [com，do，dot，how，in，java，to] &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;参考构造函数 - Class :: new&lt;/h3&gt; &lt;p&gt;可以更新第一种方法来创建从1到100的整数列表。使用lambda表达式相当容易。要创建一个新的实例ArrayList，我们有使用ArrayList::new。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; integers = IntStream .range(1, 100) .boxed() .collect(Collectors.toCollection( ArrayList::new ));   Optional&amp;lt;Integer&amp;gt; max = integers.stream().reduce(Math::max);   max.ifPresent(System.out::println); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;输出：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;99 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是Java 8 lambda增强功能中的4种类型的方法引用。&lt;/p&gt; &lt;h2&gt;Java 8默认方法教程&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;我们了解了Lambda表达式和函数式接口。现在，让我们继续讨论，并谈谈另一个相关的功能，即默认方法。那么这对java开发者来说真的是革命性的。直到java 7，我们已经了解了很多关于接口的事情，所有这些事情都在我们的头脑中，每当我们编写代码或设计应用程序。在引入默认方法后，其中一些概念将从java 8大幅改变。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h3&gt;java 8中的默认方法是什么？&lt;/h3&gt; &lt;p&gt;默认方法使您能够向库的接口添加新功能，并确保与旧版本的这些接口编写的代码的二进制兼容性。 顾名思义，java 8中的默认方法是默认的。如果不覆盖它们，则它们将由调用者类调用的方法。它们在接口中定义。&lt;/p&gt; &lt;p&gt;我们来看一个例子：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public interface Moveable {     default void move(){         System.out.println(&amp;quot;I am moving&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可移动界面定义了一个方法move（）; 并提供了默认实现。如果任何类实现了这个接口，那么它不需要实现它自己的move（）方法。它可以直接调用instance.move（）;&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Animal implements Moveable{     public static void main(String[] args){         Animal tiger = new Animal();         tiger.move();     } }  Output: I am moving &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而且如果类愿意自定义行为，那么它可以提供它自己的自定义实现并覆盖该方法。现在自己定制的方法会被调用。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Animal implements Moveable{          public void move(){         System.out.println(&amp;quot;I am running&amp;quot;);     }          public static void main(String[] args){         Animal tiger = new Animal();         tiger.move();     } }  Output: I am running &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这不是在这里完成的。最好的部分来自以下好处：&lt;/p&gt; &lt;p/&gt; 1.静态默认方法：可以在接口中定义静态默认方法，该方法对于实现此接口的所有类的实例都可用。这使您更容易在库中组织帮助方法; 您可以将静态方法保留在同一接口中的接口，而不是单独的类中。这使您能够定义类的方法，然后与所有子类共享。 &lt;p/&gt; 2.它们为您提供了一种非常需要的功能，即使在不接触代码的情况下也可以添加多个类的功能。只需在界面中添加一个默认方法即可实现。 &lt;h3&gt;为什么java 8中需要默认的方法？&lt;/h3&gt; &lt;p&gt;这是下一个面试问题的好候选人。最简单的答案是在java中启用lambda表达式的功能。Lambda表达式基本上是函数式接口的类型。为了无缝地支持lambda表达式，所有的核心类都必须被修改。但是，像java.util.List这样的核心类不仅在JDK类中实现，而且还在数千个客户端代码中实现。核心课程的任何不相容的变化肯定会回火，根本不会被接受。&lt;/p&gt; &lt;p&gt;默认方法打破了这个死锁，并允许在核心类中添加对函数式接口的支持。我们来看一个例子。下面是一个添加到java.lang.Iterable的方法。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;default void forEach(Consumer&amp;lt;? super T&amp;gt; action) {  Objects.requireNonNull(action);  for (T t : this) {   action.accept(t);  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在java 8之前，如果你必须迭代一个java集合，那么你会得到一个迭代器实例，并调用它的下一个方法，直到hasNext（）返回false。这是常见的代码，已被我们日常使用的数千次使用。语法也是一样的。所以我们可以使它紧凑，所以它只需要一行代码，仍然像以前一样为我们做这个工作。以上功能是这样做的。&lt;/p&gt; &lt;p&gt;现在要对列表中的每个项目进行迭代和执行一些简单的操作，所有你需要做的是：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;import java.util.ArrayList; import java.util.List;      public class Animal implements Moveable{         public static void main(String[] args){             List&amp;lt;Animal&amp;gt; list = new ArrayList();             list.add(new Animal());             list.add(new Animal());             list.add(new Animal());                          //Iterator code reduced to one line             list.forEach((Moveable p) -&amp;gt; p.move());         }     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;所以在这里，一个额外的方法已添加到列表中，而不会破坏它的任何自定义实现。很久以来，它一直非常需要java的功能。现在跟我们在一起&lt;/p&gt; &lt;h3&gt;调用默认方法时如何解决冲突？&lt;/h3&gt; &lt;p&gt;到现在为止还挺好。我们所有的基础知识都很好。现在转向复杂的事情。在java中，一个类可以实现N个接口。另外，接口也可以扩展另一个接口。如果任何默认方法在由单个类实现的两个这样的接口中声明。那么明显的类会混淆哪个方法来调用。&lt;/p&gt; &lt;p&gt;此冲突解决的规则如下：&lt;/p&gt; &lt;p/&gt;1）最喜欢的是在类中被覆盖的方法。如果在匹配任何东西之前找到，它们将被匹配并调用。 &lt;p/&gt;2）选择“最具体的默认提供界面”中具有相同签名的方法。这意味着如果类Animal实现了两个接口，即可移动和可移动，以便Walkable扩展Moveable。那么Walkable是这里最具体的界面，如果方法签名匹配，将从这里选择默认方法。 3）如果“可移动”和“可步行”是独立的接口，那么会发生严重的冲突情况，编译器会抱怨然后无法确定。您必须通过提供额外的信息帮助编译器，从哪个接口应该调用默认方法。例如 &lt;pre&gt;&lt;code&gt;Walkable.super.move(); //or  Moveable.super.move(); &lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Java 8函数式接口教程&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;我们了解了lambda表达式和功能接口的一些基础知识。在本教程中，我将在函数式接口的上下文中扩展主题。&lt;/p&gt; &lt;/blockquote&gt; &lt;hr /&gt; &lt;h3&gt;什么是函数式接口&lt;/h3&gt; &lt;p&gt;函数式接口是java 8中的新增功能，它们在其中只允许一个抽象方法。这些接口也称为单抽象方法接口（SAM接口）。这些可以使用Lambda表达式，方法引用和构造函数引用来表示。Java 8引入了一个注释，即@FunctionalInterface，当您注释的界面违反了函数式接口的合同时，可以将其用于编译器级错误。&lt;/p&gt; &lt;p&gt;我们来构建我们的第一个函数式接口：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    package functionalInterfaceExample;           @FunctionalInterface     public interface MyFirstFunctionalInterface {     public void firstWork();     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们尝试添加另一个抽象界面：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    package functionalInterfaceExample;           @FunctionalInterface     public interface MyFirstFunctionalInterface {     public void firstWork();     public void doSomeMoreWork();     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上将导致下面给出的编译器错误：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    Unexpected @FunctionalInterface annotation     @FunctionalInterface ^ MyFirstFunctionalInterface is not a functional interface     multiple non-overriding abstract methods found in interface MyFirstFunctionalInterface &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;img src="https://img.maruifu.com/images/blog/2017/10/toc331rnveh0rqlupeg1tb2m2p.png" alt="功能接口，错误" title="功能接口，错误" /&gt;&lt;/p&gt; &lt;h3&gt;Do not do函数式接口&lt;/h3&gt; &lt;p&gt;以下是允许的东西和哪些不在函数式接口中的列表。&lt;/p&gt; &lt;p&gt;A）如上所述，在任何函数式接口中只允许一种抽象方法。函数式接口中不允许使用第二抽象方法。如果我们删除@FunctionInterface注释，那么我们被允许添加另一个抽象方法，但它将使接口非功能接口。&lt;/p&gt; &lt;p&gt;B）即使省略@FunctionalInterface注解，函数式接口也是有效的。它仅用于通知编译器在界面内强制执行单个抽象方法。&lt;/p&gt; &lt;p&gt;C）在概念上，函数式接口只有一个抽象方法。由于默认方法有一个实现，它们不是抽象的。由于默认方法不是抽象的，您可以随意添加默认方法到您的函数式接口，尽可能多的你喜欢。 以下是有效的函数式接口：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    package functionalInterfaceExample;           @FunctionalInterface     public interface MyFirstFunctionalInterface {     public void firstWork();     default void doSomeMoreWork1(){     //Method body     }     default void doSomeMoreWork2(){     //Method body     }     } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;D）如果接口声明一个覆盖java.lang.Object的公共方法之一的抽象方法，那么这个方法也不会计入接口的抽象方法计数，因为接口的任何实现都将具有java.lang.Object或别处。比如，Comparator是一个函数式接口，尽管它声明了两种抽象方法。为什么？因为这些抽象方法之一“equals（）”在Object类中具有与public方法相同的签名。&lt;/p&gt; &lt;p&gt;例如下面的界面是一个有效的函数式接口。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    package functionalInterfaceExample;           @FunctionalInterface     public interface MyFirstFunctionalInterface {     public void firstWork();     @Override     public String toString();                //Overridden from Object class     @Override     public boolean equals(Object obj);        //Overridden from Object class     } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Fri, 27 Oct 2017 01:45:22 GMT</pubDate>
    </item>
    <item>
      <title>eclipse maven 项目 maven build 无反应</title>
      <link>https://maruifu.cn/article/41</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;eclipse maven 项目 使用maven build ，clean  等命令均无反应，控制台无任何输出&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p/&gt;1、打开Window --&gt; Preferences --&gt; Java --&gt; Installed JREs &lt;p/&gt;2、选择编辑installed JREs &lt;p/&gt;3、在Default VM arguments行加入下面的值 &lt;pre&gt;&lt;code&gt;-Dmaven.multiModuleProjectDirectory=$MAVEN_HOME ```1. &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 26 Oct 2017 05:19:41 GMT</pubDate>
    </item>
    <item>
      <title>eclipse安装lombok插件</title>
      <link>https://maruifu.cn/article/40</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;eclipse安装lombok插件&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p/&gt;1、下载Lombok.jar http://projectlombok.googlecode.com/files/lombok.jar  &lt;p/&gt;2、运行Lombok.jar: java -jar  D:\001_software\work\Java\libs\lombok.jar          数秒后将弹出一框，以确认eclipse的安装路径 &lt;p/&gt;3、确认完eclipse的安装路径后，点击install/update按钮，即可安装完成 &lt;p/&gt;4、安装完成之后，请确认eclipse安装路径下是否多了一个lombok.jar包，并且其      配置文件eclipse.ini中是否 添加了如下内容:            -javaagent:lombok.jar            -Xbootclasspath/a:lombok.jar      如果上面的答案均为true，那么恭喜你已经安装成功，否则将缺少的部分添加到相应的位置即可 &lt;p/&gt;5、重启eclipse或myeclipse &lt;p/&gt;6、创建一个java工程，建立如下类： &lt;pre&gt;&lt;code class="language-java"&gt; import lombok.Data;   import lombok.Getter;   import lombok.Setter;      @Data   public class DataObject {        private String id;           @Setter@Getter        private String name;           private String userId;           private String password;     }    &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;lombok 注解：&lt;/h3&gt; &lt;p&gt;lombok 提供的注解不多，可以参考官方视频的讲解和官方文档。 Lombok 注解在线帮助文档：http://projectlombok.org/features/index. 下面介绍几个我常用的 lombok 注解：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    @Data   ：注解在类上；提供类所有属性的 getting 和 setting 方法，此外还提供了equals、canEqual、hashCode、toString 方法     @Setter：注解在属性上；为属性提供 setting 方法     @Getter：注解在属性上；为属性提供 getting 方法     @Log4j ：注解在类上；为类提供一个 属性名为log 的 log4j 日志对象     @NoArgsConstructor：注解在类上；为类提供一个无参的构造方法     @AllArgsConstructor：注解在类上；为类提供一个全参的构造方法 &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;有问题请看:lombok 的官方网址：http://projectlombok.org/&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 25 Oct 2017 03:19:55 GMT</pubDate>
    </item>
    <item>
      <title>Github访问慢解决办法</title>
      <link>https://maruifu.cn/article/39</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Github访问慢是不是?&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h3&gt;原因&lt;/h3&gt; &lt;p&gt;为什么慢？github的CDN被某墙屏了。&lt;/p&gt; &lt;h3&gt;解决方法&lt;/h3&gt; &lt;p&gt;绕过dns解析，在本地直接绑定host，该方法也可加速其他因为CDN被屏蔽导致访问慢的网站。&lt;/p&gt; &lt;h3&gt;实现&lt;/h3&gt; &lt;p&gt;在本地host文件中添加映射，步骤如下：&lt;/p&gt; &lt;p/&gt;1.  用文本编辑器打开hosts文件，位于C:\Windows\System32\drivers\etc目录下 &lt;p/&gt; 2. 打开 http://tool.chinaz.com/dns ,这是一个查询域名映射关系的工具 &lt;p/&gt; 3. 查询 github.global.ssl.fastly.net 和 assets-cdn.github.com 两个地址 &lt;p/&gt;4. 多查几次，选择一个稳定，延迟较低的 ip 按如下方式添加到host文件 &lt;p/&gt; 5. 保存文件，重新打开浏览器，起飞。 &lt;pre&gt;&lt;code&gt; ......  # For example: # #      102.54.94.97     rhino.acme.com          # source server #       38.25.63.10     x.acme.com              # x client host  # localhost name resolution is handled within DNS itself. #   127.0.0.1       localhost #   ::1             localhost  # github 192.30.253.112 assets-cdn.github.com 151.101.88.249 github.global.ssl.fastly.net  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 25 Oct 2017 02:07:10 GMT</pubDate>
    </item>
    <item>
      <title>Eclipse无法启动报An internal error occurred during:</title>
      <link>https://maruifu.cn/article/38</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;这种错误，大概是因为，在使用eclipse中使用maven下载jar，由于太慢自己没有耐心，就手动停止，结果eclipse卡死没有响应，最后启动任务管理器结束eclipse进程。之后再次启动就出现这种结果。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;ol&gt; &lt;li&gt;在workspace中找到.metadata文件夹&lt;/li&gt; &lt;li&gt;再找到.plugins文件夹&lt;/li&gt; &lt;li&gt;再找到org.eclipse.e4.workbench文件夹&lt;/li&gt; &lt;li&gt;再把里面的workbench.xmi文件，删除就可以啦！&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;经过我实践是可以的，不过一些配置可能要重新配置，比如字体的大小。&lt;/p&gt; &lt;p&gt;注意：网上也有说直接把.metadata文件夹删除，这种方式只适合菜鸟，一般真正程序员eclipse里面已经加载了很多项目，把.metadata文件夹删除会导致要重新导入项目的情况，所以不推荐使用。（况且我删除后，并没有解决问题）&lt;/p&gt;</content:encoded>
      <pubDate>Wed, 25 Oct 2017 02:02:29 GMT</pubDate>
    </item>
    <item>
      <title>Java 8 Lambda 表达式</title>
      <link>https://maruifu.cn/article/37</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Lambda 表达式，也可称为闭包，它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数（函数作为参数传递进方法中）。 使用 Lambda 表达式可以使代码变的更加简洁紧凑。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h3&gt;语法&lt;/h3&gt; &lt;p&gt;lambda 表达式的语法格式如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;(parameters) -&amp;gt; expression 或 (parameters) -&amp;gt;{ statements; } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以下是lambda表达式的重要特征:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;可选类型声明：不需要声明参数类型，编译器可以统一识别参数值。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可选的参数圆括号：一个参数无需定义圆括号，但多个参数需要定义圆括号。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可选的大括号：如果主体包含了一个语句，就不需要使用大括号。&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;可选的返回关键字：如果主体只有一个表达式返回值则编译器会自动返回值，大括号需要指定明表达式返回了一个数值。&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Lambda 表达式实例&lt;/h3&gt; &lt;p&gt;在 Java8Tester.java 文件输入以下代码： Java8Tester.java 文件&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;    public class Java8Tester {        public static void main(String args[]){       Java8Tester tester = new Java8Tester();            // 类型声明       MathOperation addition = (int a, int b) -&amp;gt; a + b;            // 不用类型声明       MathOperation subtraction = (a, b) -&amp;gt; a - b;            // 大括号中的返回语句       MathOperation multiplication = (int a, int b) -&amp;gt; { return a * b; };            // 没有大括号及返回语句       MathOperation division = (int a, int b) -&amp;gt; a / b;            System.out.println(&amp;quot;10 + 5 = &amp;quot; + tester.operate(10, 5, addition));       System.out.println(&amp;quot;10 - 5 = &amp;quot; + tester.operate(10, 5, subtraction));       System.out.println(&amp;quot;10 x 5 = &amp;quot; + tester.operate(10, 5, multiplication));       System.out.println(&amp;quot;10 / 5 = &amp;quot; + tester.operate(10, 5, division));            // 不用括号       GreetingService greetService1 = message -&amp;gt;       System.out.println(&amp;quot;Hello &amp;quot; + message);            // 用括号       GreetingService greetService2 = (message) -&amp;gt;       System.out.println(&amp;quot;Hello &amp;quot; + message);            greetService1.sayMessage(&amp;quot;Runoob&amp;quot;);       greetService2.sayMessage(&amp;quot;Google&amp;quot;);        }             interface MathOperation {       int operation(int a, int b);        }             interface GreetingService {       void sayMessage(String message);        }             private int operate(int a, int b, MathOperation mathOperation){       return mathOperation.operation(a, b);        }     }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行以上脚本，输出结果为：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ javac Java8Tester.java  $ java Java8Tester 10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello Runoob Hello Google &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;使用 Lambda 表达式需要注意以下两点： Lambda 表达式主要用来定义行内执行的方法类型接口，例如，一个简单方法接口。在上面例子中，我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。 Lambda 表达式免去了使用匿名方法的麻烦，并且给予Java简单但是强大的函数化的编程能力。&lt;/p&gt; &lt;h3&gt;变量作用域&lt;/h3&gt; &lt;p&gt;lambda 表达式只能引用 final 或 final 局部变量，这就是说不能在 lambda 内部修改定义在域外的变量，否则会编译错误。 在 Java8Tester.java 文件输入以下代码： Java8Tester.java 文件&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Java8Tester {      final static String salutation = &amp;quot;Hello! &amp;quot;;        public static void main(String args[]){   GreetingService greetService1 = message -&amp;gt;    System.out.println(salutation + message);   greetService1.sayMessage(&amp;quot;Runoob&amp;quot;);    }     interface GreetingService {   void sayMessage(String message);    } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行以上脚本，输出结果为：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ javac Java8Tester.java  $ java Java8Tester Hello! Runoob &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在弄一个简单的例子&lt;/p&gt; &lt;p&gt;两个集合合并到一块并去重&lt;/p&gt; &lt;pre&gt;&lt;code&gt;      public static void main(String[] args) {           Ren ren1 = new Ren();         ren1.setAge(1);         ren1.setName(&amp;quot;测试1&amp;quot;);         Ren ren2 = new Ren();         ren2.setAge(2);         ren2.setName(&amp;quot;测试2&amp;quot;);         List&amp;lt;Ren&amp;gt;  list1 =  Arrays.asList(ren1,ren2);          Ren ren3 = new Ren();         ren3.setAge(3);         ren3.setName(&amp;quot;测试3&amp;quot;);         Ren ren4 = new Ren();         ren4.setAge(4);         ren4.setName(&amp;quot;测试4&amp;quot;); //        List&amp;lt;Ren&amp;gt;  list2 =  new ArrayList&amp;lt;&amp;gt;();         List&amp;lt;Ren&amp;gt;  list2 =  Arrays.asList(ren3,ren4);         List&amp;lt;Ren&amp;gt; renList = Stream.of(list1, list1).flatMap(Collection::stream).distinct().collect(Collectors.toList());         renList.forEach(System.out::println);          }     static class  Ren{         private String name;         private Integer age;          @Override         public String toString() {             return &amp;quot;Ren{&amp;quot; +                     &amp;quot;name='&amp;quot; + name + '\'' +                     &amp;quot;, age=&amp;quot; + age +                     '}';         }          public String getName() {             return name;         }          public void setName(String name) {             this.name = name;         }          public Integer getAge() {             return age;         }          public void setAge(Integer age) {             this.age = age;         }     }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;集合中的对象多个字段排序&lt;/p&gt; &lt;pre&gt;&lt;code&gt;     //排序     public  List&amp;lt;CurriculumRespDTO&amp;gt; getSortList1(List&amp;lt;CurriculumRespDTO&amp;gt; list) {         Collections.sort(list,(o1, o2)-&amp;gt; (o1.getScheduleDay() + o1.getStartTime() ).compareTo(o2.getScheduleDay() + o2.getStartTime())    );         return list;     }  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Wed, 25 Oct 2017 00:06:10 GMT</pubDate>
    </item>
    <item>
      <title>Java内部类的使用小结</title>
      <link>https://maruifu.cn/article/35</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;内部类是指在一个外部类的内部再定义一个类。类名不需要和文件夹相同。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;ul&gt; &lt;li&gt;内部类可以是静态static的，也可用public，default，protected和private修饰。（而外部顶级类即类名和文件名相同的只能使用public和default）。*&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;注意：内部类是一个编译时的概念，一旦编译成功，就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。所以内部类的成员变量/方法名可以和外部类的相同。&lt;/p&gt; &lt;h3&gt;成员内部类&lt;/h3&gt; &lt;p&gt;成员内部类，就是作为外部类的成员，可以直接使用外部类的所有成员和方法，即使是private的。同时外部类要访问内部类的所有成员变量/方法，则需要通过内部类的对象来获取。 要注意的是，成员内部类不能含有static的变量和方法。因为成员内部类需要先创建了外部类，才能创建它自己的，了解这一点，就可以明白更多事情，在此省略更多的细节了。 在成员内部类要引用外部类对象时，使用outer.this来表示外部类对象； 而需要创建内部类对象，可以使用outer.inner  obj = outerobj.new inner();&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Outer {    public static void main(String[] args) {     Outer outer = new Outer();     Outer.Inner inner = outer.new Inner();     inner.print(&amp;quot;Outer.new&amp;quot;);          inner = outer.getInner();     inner.print(&amp;quot;Outer.get&amp;quot;);    }        //个人推荐使用getxxx()来获取成员内部类，尤其是该内部类的构造函数无参数时    public Inner getInner() {     return new Inner();    }        public class Inner {     public void print(String str) {      System.out.println(str);     }    }  }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;局部内部类&lt;/h3&gt; &lt;p&gt;局部内部类，是指内部类定义在方法和作用域内。Thinking in Java给了这么两个例子： 定义在方法内：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Parcel4 {    public Destination destination(String s) {     class PDestination implements Destination {      private String label;            private PDestination(String whereTo) {       label = whereTo;      }           public String readLabel() {       return label;      }     }     return new PDestination(s);    }        public static void main(String[] args) {     Parcel4 p = new Parcel4();     Destination d = p.destination(&amp;quot;Tasmania&amp;quot;);    }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;定义在作用域里：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Parcel5 {    private void internalTracking(boolean b) {     if (b) {      class TrackingSlip {       private String id;       TrackingSlip(String s) {        id = s;       }       String getSlip() {        return id;       }      }      TrackingSlip ts = new TrackingSlip(&amp;quot;slip&amp;quot;);      String s = ts.getSlip();     }    }        public void track() {     internalTracking(true);    }        public static void main(String[] args) {     Parcel5 p = new Parcel5();     p.track();    }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;局部内部类也像别的类一样进行编译，但只是作用域不同而已，只在该方法或条件的作用域内才能使用，退出这些作用域后无法引用的。 嵌套内部类 嵌套内部类，就是修饰为static的内部类。声明为static的内部类，不需要内部类对象和外部类对象之间的联系，就是说我们可以直接引用outer.inner，即不需要创建外部类，也不需要创建内部类。 嵌套类和普通的内部类还有一个区别：普通内部类不能有static数据和static属性，也不能包含嵌套类，但嵌套类可以。而嵌套类不能声明为private，一般声明为public，方便调用。&lt;/p&gt; &lt;h3&gt;匿名内部类&lt;/h3&gt; &lt;p&gt;有时候我为了免去给内部类命名，便倾向于使用匿名内部类，因为它没有名字。例如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;((Button) findViewById(R.id.start)).setOnClickListener(new Button.OnClickListener() {  @Override  public void onClick(View v) {  new Thread() {    @Override  public void run() {  // TODO Auto-generated method stub  }    }.start();  }  });  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;匿名内部类是不能加访问修饰符的。要注意的是，new 匿名类，这个类是要先定义的，看下面例子：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Outer {    public static void main(String[] args) {     Outer outer = new Outer();     Inner inner = outer.getInner(&amp;quot;Inner&amp;quot;, &amp;quot;gz&amp;quot;);     System.out.println(inner.getName());    }        public Inner getInner(final String name, String city) {     return new Inner() {      private String nameStr = name;            public String getName() {       return nameStr;      }     };    }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;//注释后，编译时提示类Inner找不到&lt;/p&gt; &lt;pre&gt;&lt;code&gt;/* interface Inner {  String getName();  } */  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;同时在这个例子，留意外部类的方法的形参，当所在的方法的形参需要被内部类里面使用时，该形参必须为final。这里可以看到形参name已经定义为final了，而形参city 没有被使用则不用定义为final。为什么要定义为final呢？在网上找到本人比较如同的解释： “这是一个编译器设计的问题，如果你了解java的编译原理的话很容易理解。&lt;br /&gt; 首先，内部类被编译的时候会生成一个单独的内部类的.class文件，这个文件并不与外部类在同一class文件中。&lt;br /&gt; 当外部类传的参数被内部类调用时，从java程序的角度来看是直接的调用例如：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public void dosome(final String a,final int b){     class Dosome{public void dosome(){System.out.println(a+b)}};     Dosome some=new Dosome();     some.dosome();   }   &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从代码来看好像是那个内部类直接调用的a参数和b参数，但是实际上不是，在java编译器编译以后实际的操作代码是&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class Outer$Dosome{     public Dosome(final String a,final int b){      this.Dosome$a=a;      this.Dosome$b=b;     }     public void dosome(){      System.out.println(this.Dosome$a+this.Dosome$b);     }   }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从以上代码看来，内部类并不是直接调用方法传进来的参数，而是内部类将传进来的参数通过自己的构造器备份到了自己的内部，自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。&lt;br /&gt; 这样理解就很容易得出为什么要用final了，因为两者从外表看起来是同一个东西，实际上却不是这样，如果内部类改掉了这些参数的值也不可能影响到原参数，然而这样却失去了参数的一致性，因为从编程人员的角度来看他们是同一个东西，如果编程人员在程序设计的时候在内部类中改掉参数的值，但是外部调用的时候又发现值其实没有被改掉，这就让人非常的难以理解和接受，为了避免这种尴尬的问题存在，所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。” (简单理解就是，拷贝引用，为了避免引用值发生改变，例如被外部类的方法修改等，而导致内部类得到的值不一致，于是用final来让该引用不可改变)&lt;/p&gt; &lt;p&gt;因为匿名内部类，没名字，是用默认的构造函数的，无参数的，那如果需要参数呢？则需要该类有带参数的构造函数：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;    public class Outer {    public static void main(String[] args) {     Outer outer = new Outer();     Inner inner = outer.getInner(&amp;quot;Inner&amp;quot;, &amp;quot;gz&amp;quot;);     System.out.println(inner.getName());    }        public Inner getInner(final String name, String city) {     return new Inner(name, city) {     private String nameStr = name;           public String getName() {       return nameStr;      }     };    }  }    abstract class Inner {    Inner(String name, String city) {     System.out.println(city);    }        abstract String getName();  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注意这里的形参city，由于它没有被匿名内部类直接使用，而是被抽象类Inner的构造函数所使用，所以不必定义为final。&lt;/p&gt; &lt;p&gt;而匿名内部类通过实例初始化，可以达到类似构造器的效果： public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner(&amp;quot;Inner&amp;quot;, &amp;quot;gz&amp;quot;); System.out.println(inner.getName()); System.out.println(inner.getProvince()); }&lt;/p&gt; &lt;pre&gt;&lt;code&gt; public Inner getInner(final String name, final String city) {    return new Inner() {     private String nameStr = name;     private String province;       // 实例初始化     {      if (city.equals(&amp;quot;gz&amp;quot;)) {       province = &amp;quot;gd&amp;quot;;      }else {       province = &amp;quot;&amp;quot;;      }     }       public String getName() {      return nameStr;     }       public String getProvince() {      return province;     }    };   }  }   interface Inner {  String getName();  String getProvince();  }  &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;内部类的继承&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;  内部类的继承，是指内部类被继承，普通类 extents 内部类。而这时候代码上要有点特别处理，具体看以下例子： public class InheritInner extends WithInner.Inner {    // InheritInner() 是不能通过编译的，一定要加上形参  InheritInner(WithInner wi) {  wi.super();  }    public static void main(String[] args) {  WithInner wi = new WithInner();  InheritInner obj = new InheritInner(wi);  }  }    class WithInner {  class Inner {    }  }  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看到子类的构造函数里面要使用父类的外部类对象.super();而这个对象需要从外面创建并传给形参。&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Oct 2017 00:46:47 GMT</pubDate>
    </item>
    <item>
      <title>Java匿名内部类</title>
      <link>https://maruifu.cn/article/34</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;匿名内部类也就是没有名字的内部类&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;正因为没有名字，所以匿名内部类只能使用一次，它通常用来简化代码编写&lt;/p&gt; &lt;p&gt;但使用匿名内部类还有个前提条件：必须继承一个父类或实现一个接口&lt;/p&gt; &lt;h3&gt;实例1:不使用匿名内部类来实现抽象方法&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;abstract class Person {  public abstract void eat(); }  class Child extends Person {  public void eat() {   System.out.println(&amp;quot;eat something&amp;quot;);  } }  public class Demo {  public static void main(String[] args) {   Person p = new Child();   p.eat();  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果：eat something&lt;/p&gt; &lt;p&gt;可以看到，我们用Child继承了Person类，然后实现了Child的一个实例，将其向上转型为Person类的引用&lt;/p&gt; &lt;p&gt;但是，如果此处的Child类只使用一次，那么将其编写为独立的一个类岂不是很麻烦？&lt;/p&gt; &lt;p&gt;这个时候就引入了匿名内部类&lt;/p&gt; &lt;h3&gt;实例2：匿名内部类的基本实现&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;abstract class Person {  public abstract void eat(); }  public class Demo {  public static void main(String[] args) {   Person p = new Person() {    public void eat() {     System.out.println(&amp;quot;eat something&amp;quot;);    }   };   p.eat();  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果：eat something&lt;/p&gt; &lt;p&gt;可以看到，我们直接将抽象类Person中的方法在大括号中实现了&lt;/p&gt; &lt;p&gt;这样便可以省略一个类的书写&lt;/p&gt; &lt;p&gt;并且，匿名内部类还能用于接口上&lt;/p&gt; &lt;h3&gt;实例3：在接口上使用匿名内部类&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;interface Person {  public void eat(); }  public class Demo {  public static void main(String[] args) {   Person p = new Person() {    public void eat() {     System.out.println(&amp;quot;eat something&amp;quot;);    }   };   p.eat();  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果：eat something&lt;/p&gt; &lt;p&gt;由上面的例子可以看出，只要一个类是抽象的或是一个接口，那么其子类中的方法都可以使用匿名内部类来实现&lt;/p&gt; &lt;p&gt;最常用的情况就是在多线程的实现上，因为要实现多线程必须继承Thread类或是继承Runnable接口&lt;/p&gt; &lt;h3&gt;实例4：Thread类的匿名内部类实现&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public class Demo {  public static void main(String[] args) {   Thread t = new Thread() {    public void run() {     for (int i = 1; i &amp;lt;= 5; i++) {      System.out.print(i + &amp;quot; &amp;quot;);     }    }   };   t.start();  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果：1 2 3 4 5&lt;/p&gt; &lt;h3&gt;实例5：Runnable接口的匿名内部类实现&lt;/h3&gt; &lt;pre&gt;&lt;code&gt;public class Demo {  public static void main(String[] args) {   Runnable r = new Runnable() {    public void run() {     for (int i = 1; i &amp;lt;= 5; i++) {      System.out.print(i + &amp;quot; &amp;quot;);     }    }   };   Thread t = new Thread(r);   t.start();  } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;运行结果：1 2 3 4 5&lt;/p&gt;</content:encoded>
      <pubDate>Thu, 12 Oct 2017 00:06:50 GMT</pubDate>
    </item>
    <item>
      <title>厌倦了空指针异常？考虑使用Java SE 8的Optional！</title>
      <link>https://maruifu.cn/article/32</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;使您的代码更可读，并保护它免受空指针异常。&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;h3&gt;说明&lt;/h3&gt; &lt;p&gt;一个聪明的人曾经表示，在处理空指针异常之前，你不是一个真正的Java程序员。开玩笑，空引用是许多问题的根源，因为它通常用于表示没有值。Java SE 8引入了一个新的类java.util.Optional，可以减轻其中的一些问题。&lt;/p&gt; &lt;p&gt;我们从一个例子开始，看到null的危险。我们来看一个嵌套的对象结构Computer，如图1所示。&lt;/p&gt; &lt;p&gt;&lt;img src="http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2175761.gif" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;图1：用于表示a的嵌套结构 Computer&lt;/p&gt; &lt;p&gt;以下代码可能有问题吗？&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;String version = computer.getSoundcard().getUSB().getVersion(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这段代码看起来很合理。然而，许多计算机（例如，Raspberry Pi）实际上并不附带声卡。那么结果是getSoundcard()什么呢？&lt;/p&gt; &lt;p&gt;一个常见的（bad）做法是返回null引用以指示没有声卡。不幸的是，这意味着调用getUSB()将尝试返回一个空引用的USB端口，这将导致NullPointerException运行时，并阻止程序进一步运行。想象一下，如果您的程序在客户的机器上运行; 如果程序突然失败，您的客户会说什么？ 为了给出一些历史背景，计算机科学巨人托尼·霍尔（Tony Hoare）写道：“我称之为我十亿美元的错误，这是1965年发明的无效参考。我无法抗拒放弃的诱惑一个null引用，只是因为它很容易实现。“&lt;/p&gt; &lt;p&gt;你可以做什么来防止意外的空指针异常？您可以防御并添加检查以防止取消引用，如下列代码所示：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;String version = &amp;quot;UNKNOWN&amp;quot;; if(computer != null){   Soundcard soundcard = computer.getSoundcard();   if(soundcard != null){     USB usb = soundcard.getUSB();     if(usb != null){       version = usb.getVersion();     }   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是，由于嵌套检查，您可以看到清单1中的代码很难变得非常难看。不幸的是，我们需要很多样板代码，以确保我们没有得到NullPointerException。此外，这些检查妨碍了业务逻辑，这是令人讨厌的。实际上，它们正在减少我们的程序的整体可读性。&lt;/p&gt; &lt;p&gt;此外，这是一个容易出错的过程; 如果你忘记检查一个属性可能是null怎么办？我将在本文中讨论使用null表示缺少值是错误的方法。我们需要的是更好地模拟一个价值的缺失和存在。&lt;/p&gt; &lt;p&gt;为了给出一些上下文，我们来简要介绍一下其他的编程语言。&lt;/p&gt; &lt;h3&gt;没有什么替代品？&lt;/h3&gt; &lt;p&gt;诸如Groovy之类的语言具有由“ ” 表示的安全导航操作，?.用于安全浏览潜在的空引用。（请注意，它很快被包含在C＃中，并且被提出用于Java SE 7，但没有将其纳入该版本。）它的工作原理如下： 诸如Groovy之类的语言具有由“ ” 表示的安全导航操作，?.用于安全浏览潜在的空引用。（请注意，它很快被包含在C＃中，并且被提出用于Java SE 7，但没有将其纳入该版本。）它的工作原理如下：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String version = computer?.getSoundcard()?.getUSB()?.getVersion(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在这种情况下，变量version将被分配为null，如果computer为null，或getSoundcard()返回null，或getUSB()返回null。您不需要编写复杂的嵌套条件来检查null。&lt;/p&gt; &lt;p&gt;此外，Groovy还包括Elvis操作员 “ ?:”（如果您侧身看着，您会认识到Elvis着名的头发），当需要默认值时，可以使用它。在下列情况下，如果使用安全导航运算符的表达式返回null，&amp;quot;UNKNOWN&amp;quot;则返回默认值; 否则返回可用的版本标签。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String version =  computer?.getSoundcard()?.getUSB()?.getVersion() ?: &amp;quot;UNKNOWN&amp;quot;; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;其他功能语言，如Haskell和Scala，采取不同的视图。Haskell包括一个Maybe类型，它基本上封装了一个可选的值。类型Maybe的值可以包含给定类型的值或不包含任何值。没有空引用的概念。Scala有一个类似的结构，Option[T]用于封装类型值的存在或不存在T。然后，您必须使用Option类型上可用的操作来显式检查值是否存在，这强加了“空检”的想法。你不能再“忘记这样做”，因为它是由类型系统执行的。&lt;/p&gt; &lt;p&gt;好的，我们分歧了一切，这听起来很抽象。您可能现在想知道，“那么Java SE 8呢？”&lt;/p&gt; &lt;h3&gt;Optional 简而言之&lt;/h3&gt; &lt;p&gt;Java SE 8引入了一个名为j的新类ava.util.Optional&lt;T&gt;，它来自Haskell和Scala的想法。它是一个封装可选值的类，如下面的清单2和图1所示。您可以将其Optional视为包含值或不包含值的单值容器（它被称为“空”） ，如图2所示。&lt;/p&gt; &lt;p&gt;&lt;img src="http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2175762.gif" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;我们可以更新我们的模型以使用Optional public class Computer { private Optional&lt;Soundcard&gt; soundcard;&lt;br /&gt; public Optional&lt;Soundcard&gt; getSoundcard() { ... } ... }&lt;/p&gt; &lt;pre&gt;&lt;code&gt;public class Soundcard {   private Optional&amp;lt;USB&amp;gt; usb;   public Optional&amp;lt;USB&amp;gt; getUSB() { ... }  }  public class USB{   public String getVersion(){ ... } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;代码立即显示计算机可能有也可能没有声卡（声卡是可选的）。此外，声卡可以选择具有USB端口。这是一个改进，因为这个新模型现在可以清楚地反映给定值是否被允许丢失。请注意，类似的想法已经在图书馆，如番石榴。&lt;/p&gt; &lt;p&gt;但是你可以用一个Optional&lt;Soundcard&gt;对象来做什么呢？毕竟，你想要获得USB端口的版本号。简而言之，Optional该类包括明确处理值存在或不存在的情况的方法。然而，与空引用相比的优点是，Optional当该值不存在时，该类迫使您考虑该情况。因此，您可以防止意外的空指针异常。&lt;/p&gt; &lt;p&gt;重要的是要注意，Optional类的意图不是替换每个单个空引用。相反，其目的是帮助设计更易于理解的API，以便通过读取方法的签名，您可以判断是否可以期望可选的值。这迫使你主动打开一个Optional处理没有价值的东西。&lt;/p&gt; &lt;h3&gt;采用模式 Optional&lt;/h3&gt; &lt;p&gt;够说话 让我们看看一些代码！我们将首先探讨如何使用更改典型的空检查模式Optional。在本文结尾，您将了解如何使用Optional，如下所示，重写清单1中正在进行多个嵌套空值检查的代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String name = computer.flatMap(Computer::getSoundcard)   .flatMap(Soundcard::getUSB)   .map(USB::getVersion)   .orElse(&amp;quot;UNKNOWN&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;注意：确保刷新Java SE 8 lambdas和方法引用语法（请参阅“ &lt;a href="http://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html" target="_blank"&gt;Java 8：Lambdas&lt;/a&gt; ”）及其流流水线概念（请参阅“ &lt;a href="http://www.oraclejavamagazine-digital.com/javamagazine_open/20140304#pg51" target="_blank"&gt;使用Java SE 8 Streams处理数据&lt;/a&gt; ”）。&lt;/p&gt; &lt;h3&gt;创建Optional对象&lt;/h3&gt; &lt;p&gt;首先，你如何创建Optional对象？有几种方法：&lt;/p&gt; &lt;p&gt;这是一个空的Optional：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Optional&amp;lt;Soundcard&amp;gt; sc = Optional.empty();  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里是Optional一个非空值：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SoundCard soundcard = new Soundcard(); Optional&amp;lt;Soundcard&amp;gt; sc = Optional.of(soundcard);  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果soundcard为null，NullPointerException则会立即抛出一个（而不是在尝试访问该属性时发生潜在错误soundcard）。&lt;/p&gt; &lt;p&gt;另外，通过使用ofNullable，您可以创建一个Optional可能保持空值的对象：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Optional&amp;lt;Soundcard&amp;gt; sc = Optional.ofNullable(soundcard);  &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果Soundcard为空，则生成的Optional对象将为空。&lt;/p&gt; &lt;h3&gt;做某事如果价值存在&lt;/h3&gt; &lt;p&gt;现在你有一个Optional对象，你可以访问可用的方法来明确地处理值的存在或不存在。而不必记得做一个空检查，如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;SoundCard soundcard = ...; if(soundcard != null){   System.out.println(soundcard); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;您可以使用以下ifPresent()方法：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Optional&amp;lt;Soundcard&amp;gt; soundcard = ...; soundcard.ifPresent(System.out::println); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;您不再需要执行明确的空检查; 它由类型系统执行。如果Optional对象为空，则不会打印任何内容。&lt;/p&gt; &lt;p&gt;您还可以使用该isPresent()方法来确定Optional对象中是否存在值。另外还有一个get()方法返回Optional对象中包含的值，如果它存在的话。否则，它会抛出一个NoSuchElementException。这两种方法可以组合起来，如下，以防止异常：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;if(soundcard.isPresent()){   System.out.println(soundcard.get()); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然而，这不是推荐使用Optional（对嵌套空检查来说，这不是很大的改进），而且有更多的惯用选择，我们在下面探讨。&lt;/p&gt; &lt;h3&gt;默认值和操作&lt;/h3&gt; &lt;p&gt;典型的模式是返回默认值，如果确定操作的结果为空。一般来说，您可以使用三元运算符来实现：&lt;/p&gt; &lt;p&gt;Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard(&amp;quot;basic_sound_card&amp;quot;);&lt;/p&gt; &lt;p&gt;使用Optional对象，您可以使用orElse()方法重写此代码，该方法提供了一个默认值（如果Optional为空）：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Soundcard soundcard = maybeSoundcard.orElse(new Soundcard(&amp;quot;defaut&amp;quot;)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;类似地，您可以使用该orElseThrow()方法，而不是提供默认值（如果Optional为空）则会引发异常：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Soundcard soundcard =    maybeSoundCard.orElseThrow(IllegalStateException::new); &lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;使用filter方法拒绝某些值&lt;/h3&gt; &lt;p&gt;通常，您需要调用对象上的方法并检查某些属性。例如，您可能需要检查USB端口是否是特定版本。要以安全的方式执行此操作，您首先需要检查指向USB对象的引用是否为空，然后调用该getVersion()方法，如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;USB usb = ...; if(usb != null &amp;amp;&amp;amp; &amp;quot;3.0&amp;quot;.equals(usb.getVersion())){   System.out.println(&amp;quot;ok&amp;quot;); } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以使用对象filter上的方法重写此模式Optional，如下所示：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Optional&amp;lt;USB&amp;gt; maybeUSB = ...; maybeUSB.filter(usb -&amp;gt; &amp;quot;3.0&amp;quot;.equals(usb.getVersion()) .ifPresent(() -&amp;gt; System.out.println(&amp;quot;ok&amp;quot;)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;该filter方法使用谓词作为参数。如果一个值存在于Optional对象中，并与谓词匹配，则该filter方法返回该值; 否则返回一个空Optional对象。如果您已经使用filter该Stream接口的方法，您可能已经看到了类似的模式。&lt;/p&gt; &lt;h3&gt;使用该map方法提取和转换值&lt;/h3&gt; &lt;p&gt;另一种常见的模式是从对象中提取信息。例如，从Soundcard对象中，您可能需要提取USB对象，然后进一步检查它是否是正确的版本。你通常会写下面的代码：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;if(soundcard != null){   USB usb = soundcard.getUSB();   if(usb != null &amp;amp;&amp;amp; &amp;quot;3.0&amp;quot;.equals(usb.getVersion()){ System.out.println(&amp;quot;ok&amp;quot;);   } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们可以Soundcard使用该map方法重写“检查null和提取”（这里是对象）的这种模式。&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Optional&amp;lt;USB&amp;gt; usb = maybeSoundcard.map(Soundcard::getUSB); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;map与流一起使用的方法是直接平行的。在那里，您将一个函数传递给map方法，该方法将此函数应用于流的每个元素。但是，如果流为空，则不会发生任何事情。&lt;/p&gt; &lt;p&gt;该类的map方法Optional完全相同：内部包含的值Optional通过作为参数传递的函数进行“转换”（这里是提取USB端口的方法引用），而如果Optional为空，则不会发生任何反应。&lt;/p&gt; &lt;p&gt;最后，我们可以将map方法与filter方法结合使用，以拒绝其版本不同于3.0的USB端口：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;maybeSoundcard.map(Soundcard::getUSB)   .filter(usb -&amp;gt; &amp;quot;3.0&amp;quot;.equals(usb.getVersion())   .ifPresent(() -&amp;gt; System.out.println(&amp;quot;ok&amp;quot;)); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;真棒; 我们的代码开始看起来更接近于问题陈述，并且没有详细的null检查方式！&lt;/p&gt; &lt;h3&gt;Optional使用flatMap方法级联对象&lt;/h3&gt; &lt;p&gt;您已经看到可以重构使用的几种模式Optional。那么我们如何以安全的方式写下面的代码呢？&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String version = computer.getSoundcard().getUSB().getVersion(); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;请注意，所有这些代码都是从另一个提取一个对象，这正是该map方法的一个对象。在文章的前面，我们改变了我们的模型，所以Computer有一个Optional&lt;Soundcard&gt;和一个Soundcard有一个Optional&lt;USB&gt;，所以我们应该能够写下列内容：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String version = computer.map(Computer::getSoundcard)   .map(Soundcard::getUSB)   .map(USB::getVersion)   .orElse(&amp;quot;UNKNOWN&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;不幸的是，这段代码没有编译。为什么？可变计算机是类型Optional&lt;Computer&gt;，所以调用该map方法是完全正确的。但是，getSoundcard()返回一个类型的对象Optional&lt;Soundcard&gt;。这意味着地图操作的结果是类型的对象Optional&amp;lt;Optional&lt;Soundcard&gt;&amp;gt;。结果，调用getUSB()是无效的，因为最外层Optional包含其值Optional，当然不支持该getUSB()方法。图3说明了Optional您将获得的嵌套结构。 &lt;img src="http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2175763.gif" alt="" title="" /&gt; 那么我们如何解决这个问题呢？再次，我们可以看一下以前使用stream的方式：flatMap方法。使用流，该flatMap方法将一个函数作为参数，返回另一个流。该功能应用于流的每个元素，这将导致流的流。然而，flatMap具有通过该流的内容替换每个生成的流的效果。换句话说，由函数生成的所有单独的流被合并或“扁平化”成一个流。我们在这里想要的是类似的东西，但是我们希望将两层平铺Optional成一层。&lt;/p&gt; &lt;p&gt;好的，这是个好消息：Optional也支持一种flatMap方法。其目的是将变换函数应用于一个值Optional（就像地图操作那样），然后将所得到的两个层次平坦Optional化为一个。图4示出之间的差map和flatMap在变换函数返回一个Optional对象。 &lt;img src="http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2175764.gif" alt="" title="" /&gt;&lt;/p&gt; &lt;p&gt;图4：使用map与flatMap用Optional&lt;/p&gt; &lt;p&gt;所以，为了使我们的代码正确，我们需要重写如下使用flatMap：&lt;/p&gt; &lt;pre&gt;&lt;code&gt;String version = computer.flatMap(Computer::getSoundcard)    .flatMap(Soundcard::getUSB)    .map(USB::getVersion)    .orElse(&amp;quot;UNKNOWN&amp;quot;); &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;第一个flatMap确保Optional&lt;Soundcard&gt;返回一个而不是一个Optional&amp;lt;Optional&lt;Soundcard&gt;&amp;gt;，而第二个flatMap实现相同的目的来返回Optional&lt;USB&gt;。请注意，第三个调用只需要一个，map()因为getVersion()返回一个String而不是一个Optional对象。&lt;/p&gt; &lt;p&gt;哇！我们从编写痛苦的嵌套空白检查到编写能够组合，可读和更好地保护空指针异常的声明性代码已经走了很长的路。&lt;/p&gt; &lt;h3&gt;结论&lt;/h3&gt; &lt;p&gt;在本文中，我们已经看到了如何采用新的Java SE 8 java.util.Optional&lt;T&gt;。目的Optional不是替换代码库中的每一个空引用，而是帮助设计更好的API - 只要读取方法的签名，用户就可以判断是否期望可选的值。另外，Optional迫使你主动展开一个Optional处理没有价值的东西; 因此，您可以保护您的代码免受意外的空指针异常。&lt;/p&gt; &lt;h3&gt;Optional类使用场景&lt;/h3&gt; &lt;p&gt;Optional类应该作为可能有返回值函数的返回值类型。有人甚至建议Optional类应该改名为OptionalReturn。 Optional类不是为了避免所有的空指针类型机制。方法或构造函数输入参数强制性检查就仍然是有必要的。 在以下场景一般不建议使用Optional类。&lt;/p&gt; &lt;ul&gt; &lt;li&gt;领域模型层(非序列化)&lt;/li&gt; &lt;li&gt;数据传输对象(同上原因)&lt;/li&gt; &lt;li&gt;方法的输入参数&lt;/li&gt; &lt;li&gt;构造函数参数&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;Optional类方法参考&lt;/h3&gt; &lt;p&gt;下面摘抄Optional类的方法，供参考&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt;&lt;th align="center"&gt;序号&lt;/th&gt;&lt;th&gt;方法&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt;&lt;td align="center"&gt;1&lt;/td&gt;&lt;td&gt;static &lt;T&gt;   Optional&lt;T&gt; empty()&lt;/td&gt;&lt;td&gt;返回空的可选实例。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;2&lt;/td&gt;&lt;td&gt;boolean equals(Object,obj)&lt;/td&gt;&lt;td&gt;指示是否一些其他的对象是“等于”这个选项。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;3&lt;/td&gt;&lt;td&gt;Optional&lt;T&gt;   filter(Predicate&amp;lt;? super &lt;T&gt; predicate)&lt;/td&gt;&lt;td&gt;如果某个值存在，且该值与给定的谓词匹配，则它返回一个可选的描述值，否则返回一个空的可选值。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;4&lt;/td&gt;&lt;td&gt;&lt;U&gt;   Optional&lt;U&gt; flatMap(Function&amp;lt;? super T,Optional&lt;U&gt;&amp;gt; mapper)&lt;/td&gt;&lt;td&gt;如果存在一个值，它将提供的可选轴承映射函数应用到它，返回结果，否则返回空可选。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;5&lt;/td&gt;&lt;td&gt;T get()&lt;/td&gt;&lt;td&gt;如果一个值是可选的，返回值，否则抛出NoSuchElementException。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;6&lt;/td&gt;&lt;td&gt;int hashCode()&lt;/td&gt;&lt;td&gt;返回当前值的哈希代码值（如果有的话），如果没有值，则返回0（0）。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;7&lt;/td&gt;&lt;td&gt;void   ifPresent(Consumer&amp;lt;? super T&amp;gt; consumer)&lt;/td&gt;&lt;td&gt;如果存在一个值，它用值调用指定的消费者，否则什么也不做。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;8&lt;/td&gt;&lt;td&gt;boolean isPresent()&lt;/td&gt;&lt;td&gt;如果有一个价值存在返回true，否则为false。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;9&lt;/td&gt;&lt;td&gt;&lt;U&gt;Optional&lt;U&gt;   map(Function&amp;lt;? super T,? extends U&amp;gt; mapper)&lt;/td&gt;&lt;td&gt;如果存在一个值，则将所提供的映射函数应用于它，如果结果为非null，则返回一个可选的描述结果。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;10&lt;/td&gt;&lt;td&gt;static &lt;T&gt;   Optional&lt;T&gt; of(T value)&lt;/td&gt;&lt;td&gt;返回一个可选的指定非空值。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;11&lt;/td&gt;&lt;td&gt;static &lt;T&gt;   Optional&lt;T&gt; ofNullable(T value)&lt;/td&gt;&lt;td&gt;返回一个可选的描述指定值，如果非NULL，否则返回一个空可选。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;12&lt;/td&gt;&lt;td&gt;T orElse(T other)&lt;/td&gt;&lt;td&gt;如果目前的返回值，否则返回其他。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;13&lt;/td&gt;&lt;td&gt;T   orElseGet(Supplier&amp;lt;? extends T&amp;gt; other)&lt;/td&gt;&lt;td&gt;返回当前的值，否则调用其他，并返回该调用的结果。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;14&lt;/td&gt;&lt;td&gt;&lt;X extends   Throwable&gt; T orElseThrow(Supplier&amp;lt;? extends X&amp;gt; exceptionSupplier)&lt;/td&gt;&lt;td&gt;返回所包含的值，如果存在，则抛出由所提供的供应商创建的异常。&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td align="center"&gt;15&lt;/td&gt;&lt;td&gt;String toString()&lt;/td&gt;&lt;td&gt;返回此选项的非空字符串表示，适合调试。&lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;</content:encoded>
      <pubDate>Wed, 11 Oct 2017 04:52:58 GMT</pubDate>
    </item>
    <item>
      <title>jquery在线预览PDF文件</title>
      <link>https://maruifu.cn/article/23</link>
      <content:encoded>&lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;Blog新添加书籍页面,方便在线阅读,使用了jquery.media.js&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;html 代码如下&lt;/p&gt; &lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD XHTML 1.0 Transitional//EN&amp;quot; &amp;quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&amp;quot;&amp;gt; &amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot; xml:lang=&amp;quot;zh-CN&amp;quot; dir=&amp;quot;ltr&amp;quot;&amp;gt; &amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=UTF-8&amp;quot;/&amp;gt; &amp;lt;title&amp;gt;Online View PDF&amp;lt;/title&amp;gt; &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;http://sources.ikeepstudying.com/js/jquery-1.8.3.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;jquery.media.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;     $(function() {         $('a.media').media({width:800, height:600});     }); &amp;lt;/script&amp;gt; &amp;lt;/head&amp;gt;   &amp;lt;body&amp;gt; &amp;lt;a class=&amp;quot;media&amp;quot; href=&amp;quot;guice.pdf&amp;quot;&amp;gt;PDF File&amp;lt;/a&amp;gt; &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;查看预览：&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;a href="https://img.maruifu.com/images/blog/books/AlbbJavaKfsc.pdf" target="_blank"&gt;https://img.maruifu.com/images/blog/books/AlbbJavaKfsc.pdf&lt;/a&gt;&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;使用jquery.media.js就可以直接把一个连接到pdf文件的链接打开，满足了需求。&lt;/p&gt; &lt;p&gt;项目地址：&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;a href="http://jquery.malsup.com/media/" title="http://jquery.malsup.com/media/" target="_blank"&gt;http://jquery.malsup.com/media/&lt;/a&gt;&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;或者复制下面的代码： jquery.media.js&lt;/p&gt; &lt;pre&gt;&lt;code class="language-js"&gt;/*  * jQuery Media Plugin for converting elements into rich media content.  *  * Examples and documentation at: http://malsup.com/jquery/media/  * Copyright (c) 2007-2010 M. Alsup  * Dual licensed under the MIT and GPL licenses:  * http://www.opensource.org/licenses/mit-license.php  * http://www.gnu.org/licenses/gpl.html  *  * @author: M. Alsup  * @version: 0.99 (05-JUN-2013)  * @requires jQuery v1.1.2 or later  * $Id: jquery.media.js 2460 2007-07-23 02:53:15Z malsup $  *  * Supported Media Players:  * - Flash  * - Quicktime  * - Real Player  * - Silverlight  * - Windows Media Player  * - iframe  *  * Supported Media Formats:  *  Any types supported by the above players, such as:  *  Video: asf, avi, flv, mov, mpg, mpeg, mp4, qt, smil, swf, wmv, 3g2, 3gp  *  Audio: aif, aac, au, gsm, mid, midi, mov, mp3, m4a, snd, rm, wav, wma  *  Other: bmp, html, pdf, psd, qif, qtif, qti, tif, tiff, xaml  *  * Thanks to Mark Hicken and Brent Pedersen for helping me debug this on the Mac!  * Thanks to Dan Rossi for numerous bug reports and code bits!  * Thanks to Skye Giordano for several great suggestions!  * Thanks to Richard Connamacher for excellent improvements to the non-IE behavior!  */ /*global SWFObject alert Sys */ /*jshint forin:false */ ;(function($) { &amp;quot;use strict&amp;quot;;   var mode = document.documentMode || 0; var msie = /MSIE/.test(navigator.userAgent); var lameIE = msie &amp;amp;&amp;amp; (/MSIE (6|7|8)\.0/.test(navigator.userAgent) || mode &amp;lt; 9);  /**  * Chainable method for converting elements into rich media.  *  * @param options  * @param callback fn invoked for each matched element before conversion  * @param callback fn invoked for each matched element after conversion  */ $.fn.media = function(options, f1, f2) {  if (options == 'undo') {   return this.each(function() {    var $this = $(this);    var html = $this.data('media.origHTML');    if (html)     $this.replaceWith(html);   });  }    return this.each(function() {   if (typeof options == 'function') {    f2 = f1;    f1 = options;    options = {};   }   var o = getSettings(this, options);   // pre-conversion callback, passes original element and fully populated options   if (typeof f1 == 'function') f1(this, o);    var r = getTypesRegExp();   var m = r.exec(o.src.toLowerCase()) || [''];   var fn;    if (o.type)    m[0] = o.type;   else    m.shift();    for (var i=0; i &amp;lt; m.length; i++) {    fn = m[i].toLowerCase();    if (isDigit(fn[0])) fn = 'fn' + fn; // fns can't begin with numbers    if (!$.fn.media[fn])     continue;  // unrecognized media type    // normalize autoplay settings    var player = $.fn.media[fn+'_player'];    if (!o.params) o.params = {};    if (player) {     var num = player.autoplayAttr == 'autostart';     o.params[player.autoplayAttr || 'autoplay'] = num ? (o.autoplay ? 1 : 0) : o.autoplay ? true : false;    }    var $div = $.fn.media[fn](this, o);     $div.css('backgroundColor', o.bgColor).width(o.width);        if (o.canUndo) {     var $temp = $('&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;').append(this);     $div.data('media.origHTML', $temp.html()); // store original markup    }        // post-conversion callback, passes original element, new div element and fully populated options    if (typeof f2 == 'function') f2(this, $div[0], o, player.name);    break;   }  }); };  /**  * Non-chainable method for adding or changing file format / player mapping  * @name mapFormat  * @param String format File format extension (ie: mov, wav, mp3)  * @param String player Player name to use for the format (one of: flash, quicktime, realplayer, winmedia, silverlight or iframe  */ $.fn.media.mapFormat = function(format, player) {  if (!format || !player || !$.fn.media.defaults.players[player]) return; // invalid  format = format.toLowerCase();  if (isDigit(format[0])) format = 'fn' + format;  $.fn.media[format] = $.fn.media[player];  $.fn.media[format+'_player'] = $.fn.media.defaults.players[player]; };  // global defautls; override as needed $.fn.media.defaults = {  standards:  true,       // use object tags only (no embeds for non-IE browsers)  canUndo:    true,       // tells plugin to store the original markup so it can be reverted via: $(sel).mediaUndo()  width:  400,  height:  400,  autoplay: 0,   // normalized cross-player setting  bgColor: '#ffffff', // background color  params:  { wmode: 'transparent'}, // added to object element as param elements; added to embed element as attrs  attrs:  {},   // added to object and embed elements as attrs  flvKeyName: 'file',  // key used for object src param (thanks to Andrea Ercolino)  flashvars: {},   // added to flash content as flashvars param/attr  flashVersion: '7', // required flash version  expressInstaller: null, // src for express installer   // default flash video and mp3 player (@see: http://jeroenwijering.com/?item=Flash_Media_Player)  flvPlayer:  'mediaplayer.swf',  mp3Player:  'mediaplayer.swf',   // @see http://msdn2.microsoft.com/en-us/library/bb412401.aspx  silverlight: {   inplaceInstallPrompt: 'true', // display in-place install prompt?   isWindowless:    'true', // windowless mode (false for wrapping markup)   framerate:     '24',   // maximum framerate   version:     '0.9',  // Silverlight version   onError:     null,   // onError callback   onLoad:         null,   // onLoad callback   initParams:     null,   // object init params   userContext:    null   // callback arg passed to the load callback  } };  // Media Players; think twice before overriding $.fn.media.defaults.players = {  flash: {   name:   'flash',   title:   'Flash',   types:   'flv,mp3,swf',   mimetype:  'application/x-shockwave-flash',   pluginspage: 'http://www.adobe.com/go/getflashplayer',   ieAttrs: {    classid:  'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',    type:   'application/x-oleobject',    codebase: 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=' + $.fn.media.defaults.flashVersion   }  },  quicktime: {   name:   'quicktime',   title:   'QuickTime',   mimetype:  'video/quicktime',   pluginspage: 'http://www.apple.com/quicktime/download/',   types:   'aif,aiff,aac,au,bmp,gsm,mov,mid,midi,mpg,mpeg,mp4,m4a,psd,qt,qtif,qif,qti,snd,tif,tiff,wav,3g2,3gp',   ieAttrs: {    classid:  'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B',    codebase: 'http://www.apple.com/qtactivex/qtplugin.cab'   }  },  realplayer: {   name:    'real',   title:    'RealPlayer',   types:    'ra,ram,rm,rpm,rv,smi,smil',   mimetype:   'audio/x-pn-realaudio-plugin',   pluginspage:  'http://www.real.com/player/',   autoplayAttr: 'autostart',   ieAttrs: {    classid: 'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'   }  },  winmedia: {   name:    'winmedia',   title:    'Windows Media',   types:    'asx,asf,avi,wma,wmv',   mimetype:   isFirefoxWMPPluginInstalled() ? 'application/x-ms-wmp' : 'application/x-mplayer2',   pluginspage:  'http://www.microsoft.com/Windows/MediaPlayer/',   autoplayAttr: 'autostart',   oUrl:    'url',   ieAttrs: {    classid:  'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6',    type:   'application/x-oleobject'   }  },  // special cases  img: {   name:  'img',   title: 'Image',   types: 'gif,png,jpg'  },  iframe: {   name:  'iframe',   types: 'html,pdf'  },  silverlight: {   name:  'silverlight',   types: 'xaml'  } };  // // everything below here is private //   // detection script for FF WMP plugin (http://www.therossman.org/experiments/wmp_play.html) // (hat tip to Mark Ross for this script) function isFirefoxWMPPluginInstalled() {  var plugs = navigator.plugins || [];  for (var i = 0; i &amp;lt; plugs.length; i++) {   var plugin = plugs[i];   if (plugin['filename'] == 'np-mswmp.dll')    return true;  }  return false; }  var counter = 1;  for (var player in $.fn.media.defaults.players) {  var types = $.fn.media.defaults.players[player].types;  $.each(types.split(','), function(i,o) {   if (isDigit(o[0])) o = 'fn' + o;   $.fn.media[o] = $.fn.media[player] = getGenerator(player);   $.fn.media[o+'_player'] = $.fn.media.defaults.players[player];  }); }  function getTypesRegExp() {  var types = '';  for (var player in $.fn.media.defaults.players) {   if (types.length) types += ',';   types += $.fn.media.defaults.players[player].types;  }  return new RegExp('\\.(' + types.replace(/,/ig,'|') + ')\\b'); }  function getGenerator(player) {  return function(el, options) {   return generate(el, options, player);  }; }  function isDigit(c) {  return '0123456789'.indexOf(c) &amp;gt; -1; }  // flatten all possible options: global defaults, meta, option obj function getSettings(el, options) {  options = options || {};  var a, n;  var $el = $(el);  var cls = el.className || '';  // support metadata plugin (v1.0 and v2.0)  var meta = $.metadata ? $el.metadata() : $.meta ? $el.data() : {};  meta = meta || {};  var w = meta.width  || parseInt(((cls.match(/\bw:(\d+)/)||[])[1]||0),10) || parseInt(((cls.match(/\bwidth:(\d+)/)||[])[1]||0),10);  var h = meta.height || parseInt(((cls.match(/\bh:(\d+)/)||[])[1]||0),10) || parseInt(((cls.match(/\bheight:(\d+)/)||[])[1]||0),10);   if (w) meta.width = w;  if (h) meta.height = h;  if (cls) meta.cls = cls;    // crank html5 style data attributes  var dataName = 'data-';     for (var i=0; i &amp;lt; el.attributes.length; i++) {         a = el.attributes[i], n = $.trim(a.name);         var index = n.indexOf(dataName);         if (index === 0) {    n = n.substring(dataName.length);    meta[n] = a.value;         }     }   a = $.fn.media.defaults;  var b = options;  var c = meta;   var p = { params: { bgColor: options.bgColor || $.fn.media.defaults.bgColor } };  var opts = $.extend({}, a, b, c);  $.each(['attrs','params','flashvars','silverlight'], function(i,o) {   opts[o] = $.extend({}, p[o] || {}, a[o] || {}, b[o] || {}, c[o] || {});  });   if (typeof opts.caption == 'undefined') opts.caption = $el.text();   // make sure we have a source!  opts.src = opts.src || $el.attr('href') || $el.attr('src') || 'unknown';  return opts; }  // // Flash Player //  // generate flash using SWFObject library if possible $.fn.media.swf = function(el, opts) {  var f, p;  if (!window.SWFObject &amp;amp;&amp;amp; !window.swfobject) {   // roll our own   if (opts.flashvars) {    var a = [];    for (f in opts.flashvars)     a.push(f + '=' + opts.flashvars[f]);    if (!opts.params) opts.params = {};    opts.params.flashvars = a.join('&amp;amp;');   }   return generate(el, opts, 'flash');  }   var id = el.id ? (' id=&amp;quot;'+el.id+'&amp;quot;') : '';  var cls = opts.cls ? (' class=&amp;quot;' + opts.cls + '&amp;quot;') : '';  var $div = $('&amp;lt;div' + id + cls + '&amp;gt;');   // swfobject v2+  if (window.swfobject) {   $(el).after($div).appendTo($div);   if (!el.id) el.id = 'movie_player_' + counter++;    // replace el with swfobject content   window.swfobject.embedSWF(opts.src, el.id, opts.width, opts.height, opts.flashVersion,    opts.expressInstaller, opts.flashvars, opts.params, opts.attrs);  }  // swfobject &amp;lt; v2  else {   $(el).after($div).remove();   var so = new SWFObject(opts.src, 'movie_player_' + counter++, opts.width, opts.height, opts.flashVersion, opts.bgColor);   if (opts.expressInstaller) so.useExpressInstall(opts.expressInstaller);    for (p in opts.params)    if (p != 'bgColor') so.addParam(p, opts.params[p]);   for (f in opts.flashvars)    so.addVariable(f, opts.flashvars[f]);   so.write($div[0]);  }   if (opts.caption) $('&amp;lt;div&amp;gt;').appendTo($div).html(opts.caption);  return $div; };  // map flv and mp3 files to the swf player by default $.fn.media.flv = $.fn.media.mp3 = function(el, opts) {  var src = opts.src;  var player = /\.mp3\b/i.test(src) ? opts.mp3Player : opts.flvPlayer;  var key = opts.flvKeyName;  src = encodeURIComponent(src);  opts.src = player;  opts.src = opts.src + '?'+key+'=' + (src);  var srcObj = {};  srcObj[key] = src;  opts.flashvars = $.extend({}, srcObj, opts.flashvars );  return $.fn.media.swf(el, opts); };  // // Silverlight // $.fn.media.xaml = function(el, opts) {  if (!window.Sys || !window.Sys.Silverlight) {   if ($.fn.media.xaml.warning) return;   $.fn.media.xaml.warning = 1;   alert('You must include the Silverlight.js script.');   return;  }   var props = {   width: opts.width,   height: opts.height,   background: opts.bgColor,   inplaceInstallPrompt: opts.silverlight.inplaceInstallPrompt,   isWindowless: opts.silverlight.isWindowless,   framerate: opts.silverlight.framerate,   version: opts.silverlight.version  };  var events = {   onError: opts.silverlight.onError,   onLoad: opts.silverlight.onLoad  };   var id1 = el.id ? (' id=&amp;quot;'+el.id+'&amp;quot;') : '';  var id2 = opts.id || 'AG' + counter++;  // convert element to div  var cls = opts.cls ? (' class=&amp;quot;' + opts.cls + '&amp;quot;') : '';  var $div = $('&amp;lt;div' + id1 + cls + '&amp;gt;');  $(el).after($div).remove();   Sys.Silverlight.createObjectEx({   source: opts.src,   initParams: opts.silverlight.initParams,   userContext: opts.silverlight.userContext,   id: id2,   parentElement: $div[0],   properties: props,   events: events  });   if (opts.caption) $('&amp;lt;div&amp;gt;').appendTo($div).html(opts.caption);  return $div; };  // // generate object/embed markup // function generate(el, opts, player) {  var $el = $(el);  var o = $.fn.media.defaults.players[player];  var a, key, v;   if (player == 'iframe') {   o = $('&amp;lt;iframe' + ' width=&amp;quot;' + opts.width + '&amp;quot; height=&amp;quot;' + opts.height + '&amp;quot; &amp;gt;');   o.attr('src', opts.src);   o.css('backgroundColor', o.bgColor);  }  else if (player == 'img') {   o = $('&amp;lt;img&amp;gt;');   o.attr('src', opts.src);   if (opts.width)    o.attr('width', opts.width);   if (opts.height)    o.attr('height', opts.height);   o.css('backgroundColor', o.bgColor);  }  else if (lameIE) {   a = ['&amp;lt;object width=&amp;quot;' + opts.width + '&amp;quot; height=&amp;quot;' + opts.height + '&amp;quot; '];   for (key in opts.attrs)    a.push(key + '=&amp;quot;'+opts.attrs[key]+'&amp;quot; ');   for (key in o.ieAttrs || {}) {    v = o.ieAttrs[key];    if (key == 'codebase' &amp;amp;&amp;amp; window.location.protocol == 'https:')     v = v.replace('http','https');    a.push(key + '=&amp;quot;'+v+'&amp;quot; ');   }   a.push('&amp;gt;&amp;lt;/ob'+'ject'+'&amp;gt;');   var p = ['&amp;lt;param name=&amp;quot;' + (o.oUrl || 'src') +'&amp;quot; value=&amp;quot;' + opts.src + '&amp;quot;&amp;gt;'];   for (key in opts.params)    p.push('&amp;lt;param name=&amp;quot;'+ key +'&amp;quot; value=&amp;quot;' + opts.params[key] + '&amp;quot;&amp;gt;');   o = document.createElement(a.join(''));   for (var i=0; i &amp;lt; p.length; i++)    o.appendChild(document.createElement(p[i]));  }  else if (opts.standards) {   // Rewritten to be standards compliant by Richard Connamacher   a = ['&amp;lt;object type=&amp;quot;' + o.mimetype +'&amp;quot; width=&amp;quot;' + opts.width + '&amp;quot; height=&amp;quot;' + opts.height +'&amp;quot;'];   if (opts.src) a.push(' data=&amp;quot;' + opts.src + '&amp;quot; ');   if (msie) {    for (key in o.ieAttrs || {}) {     v = o.ieAttrs[key];     if (key == 'codebase' &amp;amp;&amp;amp; window.location.protocol == 'https:')      v = v.replace('http','https');     a.push(key + '=&amp;quot;'+v+'&amp;quot; ');    }   }   a.push('&amp;gt;');   a.push('&amp;lt;param name=&amp;quot;' + (o.oUrl || 'src') +'&amp;quot; value=&amp;quot;' + opts.src + '&amp;quot;&amp;gt;');   for (key in opts.params) {    if (key == 'wmode' &amp;amp;&amp;amp; player != 'flash') // FF3/Quicktime borks on wmode     continue;    a.push('&amp;lt;param name=&amp;quot;'+ key +'&amp;quot; value=&amp;quot;' + opts.params[key] + '&amp;quot;&amp;gt;');   }   // Alternate HTML   a.push('&amp;lt;div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;'+o.title+' Required&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;'+o.title+' is required to view this media. &amp;lt;a href=&amp;quot;'+o.pluginspage+'&amp;quot;&amp;gt;Download Here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;');   a.push('&amp;lt;/ob'+'ject'+'&amp;gt;');  }   else {          a = ['&amp;lt;embed width=&amp;quot;' + opts.width + '&amp;quot; height=&amp;quot;' + opts.height + '&amp;quot; style=&amp;quot;display:block&amp;quot;'];          if (opts.src) a.push(' src=&amp;quot;' + opts.src + '&amp;quot; ');          for (key in opts.attrs)              a.push(key + '=&amp;quot;'+opts.attrs[key]+'&amp;quot; ');          for (key in o.eAttrs || {})              a.push(key + '=&amp;quot;'+o.eAttrs[key]+'&amp;quot; ');          for (key in opts.params) {              if (key == 'wmode' &amp;amp;&amp;amp; player != 'flash') // FF3/Quicktime borks on wmode      continue;              a.push(key + '=&amp;quot;'+opts.params[key]+'&amp;quot; ');          }          a.push('&amp;gt;&amp;lt;/em'+'bed'+'&amp;gt;');      }   // convert element to div  var id = el.id ? (' id=&amp;quot;'+el.id+'&amp;quot;') : '';  var cls = opts.cls ? (' class=&amp;quot;' + opts.cls + '&amp;quot;') : '';  var $div = $('&amp;lt;div' + id + cls + '&amp;gt;');  $el.after($div).remove();  if (lameIE || player == 'iframe' || player == 'img')   $div.append(o);  else   $div.html(a.join(''));    if (opts.caption)    $('&amp;lt;div&amp;gt;').appendTo($div).html(opts.caption);  return $div; }   })(jQuery);  &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Tue, 10 Oct 2017 08:18:59 GMT</pubDate>
    </item>
    <item>
      <title>第一次和SQLite接触</title>
      <link>https://maruifu.cn/article/22</link>
      <content:encoded>&lt;h2&gt;第一次使用SQlite.&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;如此神奇的数据库...&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;要使用java程序连接SQLite，并与SQLite进行数据存取操作，必须在系统上设置SQLite JDBC驱动程序和安装Java JDK。按照以下步骤进行：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;从sqlite-jdbc存储库下载最新版本的 ***&lt;a href="http://bitbucket.org/xerial/sqlite-jdbc/downloads" target="_blank"&gt;sqlite-jdbc-(VERSION).jar&lt;/a&gt;***。&lt;/li&gt; &lt;li&gt;将下载的jar文件添加到类路径。&lt;/li&gt; &lt;li&gt;使用java连接到SQLite数据库。&lt;/li&gt; &lt;/ul&gt; &lt;h3&gt;连接到SQLite数据库&lt;/h3&gt; &lt;p&gt;打开Eclipse IDE，创建一个JAVA工程：JavaWithSQLite，下载所需的sqlite-jdbc-(VERSION).jar(下载地址： &lt;strong&gt;&lt;a href="http://bitbucket.org/xerial/sqlite-jdbc/downloads" target="_blank"&gt;http://bitbucket.org/xerial/sqlite-jdbc/downloads/&lt;/a&gt;&lt;/strong&gt; 或者 &lt;strong&gt;&lt;a href="http://mvnrepository.com/artifact/org.xerial/sqlite-jdbc" target="_blank"&gt;http://mvnrepository.com/artifact/org.xerial/sqlite-jdbc&lt;/a&gt;&lt;/strong&gt; )，并将它放入到项目的类库中。&lt;/p&gt; &lt;p&gt;使用以下代码使用Java编程语言连接到SQLite数据库，首先创建一个类：ConnectSQLite.java，其代码如下所示 -&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.sqlite;  import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;  public class ConnectSQLite {     /**      * Connect to a sample database      */     public static void connect() {         Connection conn = null;         try {             // db parameters             String url = &amp;quot;jdbc:sqlite:D:/sqlite/java-sqlite.db&amp;quot;;             // create a connection to the database             conn = DriverManager.getConnection(url);              System.out.println(&amp;quot;Connection to SQLite has been established.&amp;quot;);          } catch (SQLException e) {             System.out.println(e.getMessage());         } finally {             try {                 if (conn != null) {                     conn.close();                 }             } catch (SQLException ex) {                 System.out.println(ex.getMessage());             }         }     }      /**      * @param args      *            the command line arguments      */     public static void main(String[] args) {         connect();     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行上面代码后，会创建一个文件：D:/sqlite/java-sqlite.db，并与数据库java-sqlite.db连接。 &lt;img src="https://img.maruifu.com/images/blog/2017/10/243rdgsdckhi3rmvsmeaqnmua9.png" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;使用java创建数据库&lt;/h3&gt; &lt;p&gt;还可以使用java编程语言在SQLite中创建一个新的数据库。假设要使用Java来创建一个名为java_sqlite.db的数据库。创建一个公共类：CreateDB.java并使用以下代码：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.sqlite;  import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.SQLException;  public class CreateDB {      public static void createNewDatabase(String fileName) {          String url = &amp;quot;jdbc:sqlite:&amp;quot; + fileName;          try {             Connection conn = DriverManager.getConnection(url);             if (conn != null) {                 DatabaseMetaData meta = conn.getMetaData();                 System.out.println(&amp;quot;The driver name is &amp;quot; + meta.getDriverName());                 System.out.println(&amp;quot;A new database has been created.&amp;quot;);             }          } catch (SQLException e) {             System.out.println(e.getMessage());         }     }      public static void main(String[] args) {         createNewDatabase(&amp;quot;D:/sqlite/create-db.db&amp;quot;);     } } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行上面代码，得到以下结果 -&lt;/p&gt; &lt;pre&gt;&lt;code&gt;The driver name is SQLiteJDBC A new database has been created. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在创建一个名为“create-db.db”的新数据库。可以看到对应创建目录有一个文件：create-db.db存在。 &lt;img src="https://img.maruifu.com/images/blog/2017/10/pscio881jagqpp0eb1em7l9jl1.png" alt="alt" title="alt" /&gt;&lt;/p&gt; &lt;h3&gt;使用java创建一个表&lt;/h3&gt; &lt;p&gt;假设要通过Java程序在SQLite中创建一个名为tb_emp的表，tb_emp表具有id,name和capacity这三列。首先创建一个Java类：CreateTable.java，并使用以下代码&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.sqlite; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement;  public class CreateTable {      public static void createNewTable() {         // SQLite connection string         String url = &amp;quot;jdbc:sqlite:D:/sqlite/java-sqlite.db&amp;quot;;          // SQL statement for creating a new table         String sql = &amp;quot;CREATE TABLE IF NOT EXISTS employees (\n&amp;quot; + &amp;quot; id integer PRIMARY KEY,\n&amp;quot;                 + &amp;quot; name text NOT NULL,\n&amp;quot; + &amp;quot; capacity real\n&amp;quot; + &amp;quot;);&amp;quot;;          try {             Connection conn = DriverManager.getConnection(url);             Statement stmt = conn.createStatement();             stmt.execute(sql);             System.out.println(&amp;quot;Create table finished.&amp;quot;);         } catch (SQLException e) {             System.out.println(e.getMessage());         }     }      /**      * @param args      *            the command line arguments      */     public static void main(String[] args) {         createNewTable();     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行上面代码，得到以下结果 -&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Create table finished. &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行上面代码后，将在java-sqlite.db数据库中创建一个名称为：employees 的表。&lt;/p&gt; &lt;h3&gt;向表中插入记录&lt;/h3&gt; &lt;p&gt;创建表后，使用以下代码在表中插入一些记录。 创建一个新的Java类：InsertRecords，具有以下代码：&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.sqlite;  import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException;  public class InsertRecords {      private Connection connect() {         // SQLite connection string         String url = &amp;quot;jdbc:sqlite:D:/sqlite/java-sqlite.db&amp;quot;;         Connection conn = null;         try {             conn = DriverManager.getConnection(url);         } catch (SQLException e) {             System.out.println(e.getMessage());         }         return conn;     }      public void insert(String name, double capacity) {         String sql = &amp;quot;INSERT INTO employees(name, capacity) VALUES(?,?)&amp;quot;;          try {             Connection conn = this.connect();             PreparedStatement pstmt = conn.prepareStatement(sql);             pstmt.setString(1, name);             pstmt.setDouble(2, capacity);             pstmt.executeUpdate();         } catch (SQLException e) {             System.out.println(e.getMessage());         }     }      public static void main(String[] args) {          InsertRecords app = new InsertRecords();         // insert three new rows         app.insert(&amp;quot;Maxsu&amp;quot;, 30000);         app.insert(&amp;quot;Minsu&amp;quot;, 40000);         app.insert(&amp;quot;Miswong&amp;quot;, 50000);         System.out.println(&amp;quot;Insert data finished.&amp;quot;);     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在记录被插入到表中。 可以使用SELECT命令查看它： &lt;img src="https://img.maruifu.com/images/blog/2017/10/5gm9boetu6iq4pa1j6sqsa4kni.png" alt="查询结果图片" title="查询结果图片" /&gt; &lt;em&gt;!!!表中插入记录 是用客户端navicat  查询.&lt;/em&gt;&lt;/p&gt; &lt;h3&gt;查询/选择记录&lt;/h3&gt; &lt;p&gt;要使用Java程序从表中选择/查询记录，请使用以下代码。 创建一个新的Java类 - SelectRecords.java，使用以下代码 -&lt;/p&gt; &lt;pre&gt;&lt;code class="language-java"&gt;package cn.maruifu.sqlite;  import java.sql.DriverManager; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;  public class SelectRecords {      private Connection connect() {         // SQLite connection string         String url = &amp;quot;jdbc:sqlite:D:/java-sqlite.db&amp;quot;;         Connection conn = null;         try {             conn = DriverManager.getConnection(url);         } catch (SQLException e) {             System.out.println(e.getMessage());         }         return conn;     }      public void selectAll() {         String sql = &amp;quot;SELECT * FROM employees&amp;quot;;          try {             Connection conn = this.connect();             Statement stmt = conn.createStatement();             ResultSet rs = stmt.executeQuery(sql);              // loop through the result set             while (rs.next()) {                 System.out.println(rs.getInt(&amp;quot;id&amp;quot;) + &amp;quot;\t&amp;quot; + rs.getString(&amp;quot;name&amp;quot;) + &amp;quot;\t&amp;quot; + rs.getDouble(&amp;quot;capacity&amp;quot;));             }         } catch (SQLException e) {             System.out.println(e.getMessage());         }     }      /**      * @param args      *            the command line arguments      */     public static void main(String[] args) {         SelectRecords app = new SelectRecords();         app.selectAll();     }  } &lt;/code&gt;&lt;/pre&gt; &lt;p&gt;执行上面代码，得到以下结果 - &lt;img src="https://img.maruifu.com/images/blog/2017/10/7klgqnp12ggogot0trtgac09oe.png" alt="代码查询结果图片" title="代码查询结果图片" /&gt;&lt;/p&gt;</content:encoded>
      <pubDate>Tue, 10 Oct 2017 00:56:48 GMT</pubDate>
    </item>
    <item>
      <title>准备开始写博客啦!</title>
      <link>https://maruifu.cn/article/2</link>
      <content:encoded>&lt;h2&gt;小马哥的故事.&lt;/h2&gt; &lt;hr /&gt; &lt;blockquote&gt; &lt;p&gt;第一篇文章总得写点儿什么?...&lt;/p&gt; &lt;/blockquote&gt; &lt;div align="right"&gt;-----------------来自小马哥的故事&lt;/div&gt; &lt;hr /&gt; &lt;pre&gt;&lt;code class="language-java"&gt;public static void main(String[] args){     System.out.println(\&amp;quot;小马哥的故事.\&amp;quot;); } &lt;/code&gt;&lt;/pre&gt;</content:encoded>
      <pubDate>Thu, 23 Feb 2017 14:46:24 GMT</pubDate>
    </item>
  </channel>
</rss>

