<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>John Doe</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://luoyuanxiang.top/</id>
  <link href="https://luoyuanxiang.top/" rel="alternate"/>
  <link href="https://luoyuanxiang.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, John Doe</rights>
  <title>Hexo</title>
  <updated>2026-07-22T09:03:41.860Z</updated>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <category term="spring" scheme="https://luoyuanxiang.top/tags/spring/"/>
    <category term="spring boot" scheme="https://luoyuanxiang.top/tags/spring-boot/"/>
    <category term="Spring Security" scheme="https://luoyuanxiang.top/tags/Spring-Security/"/>
    <content>
      <![CDATA[<h2 id="一、自定义匿名用户访问返回处理：AnonymousAuthenticationEntryPoint"><a href="#一、自定义匿名用户访问返回处理：AnonymousAuthenticationEntryPoint" class="headerlink" title="一、自定义匿名用户访问返回处理：AnonymousAuthenticationEntryPoint"></a>一、自定义匿名用户访问返回处理：<code>AnonymousAuthenticationEntryPoint</code></h2><h3 id="核心作用"><a href="#核心作用" class="headerlink" title="核心作用"></a>核心作用</h3><p>当<strong>未登录的匿名用户</strong>访问需要认证的接口时，替代 Security 默认的「401 页面响应」，返回自定义的 JSON 格式提示（明确告知 “未登录”），适配前后端分离场景（前端需解析 JSON 做跳转或提示）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 匿名用户访问处理</span></span><br><span class="line"><span class="comment"> * 核心：拦截匿名用户的未认证请求，返回统一JSON格式的“未登录”提示</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span> <span class="comment">// 注入Spring容器，供Security配置使用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AnonymousAuthenticationEntryPoint</span> <span class="keyword">implements</span> <span class="title class_">AuthenticationEntryPoint</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">commence</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        <span class="comment">// 1. 日志记录：打印访问失败的接口路径和异常信息，方便排查问题</span></span><br><span class="line">        log.info(<span class="string">&quot;用户需要登录，访问[&#123;&#125;]失败，AuthenticationException=&#123;&#125;&quot;</span>, request.getRequestURI(), authException.getMessage(), authException);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 跨域配置：允许前端跨域请求（前后端分离必配，否则前端无法解析响应）</span></span><br><span class="line">        response.setHeader(<span class="string">&quot;Access-Control-Allow-Origin&quot;</span>, <span class="string">&quot;*&quot;</span>);</span><br><span class="line">        <span class="comment">// 3. 响应格式：指定返回JSON，避免前端解析乱码</span></span><br><span class="line">        response.setHeader(<span class="string">&quot;Content-type&quot;</span>, <span class="string">&quot;application/json;charset=UTF-8&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 自定义响应体：使用项目统一的Result工具类，状态码424（自定义，区分“未登录”和其他401场景）</span></span><br><span class="line">        Result&lt;String&gt; object = Result.error(<span class="number">424</span>, <span class="string">&quot;未登录，请登录访问&quot;</span>);</span><br><span class="line">        <span class="comment">// 5. 写入响应：将Result转为JSON字符串返回</span></span><br><span class="line">        response.getWriter().print(JSONUtil.toJsonStr(object));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="二、自定义权限访问异常处理：CustomAccessDeniedHandler"><a href="#二、自定义权限访问异常处理：CustomAccessDeniedHandler" class="headerlink" title="二、自定义权限访问异常处理：CustomAccessDeniedHandler"></a>二、自定义权限访问异常处理：<code>CustomAccessDeniedHandler</code></h2><h3 id="核心作用-1"><a href="#核心作用-1" class="headerlink" title="核心作用"></a>核心作用</h3><p>当<strong>已登录但权限不足</strong>的用户访问接口时（比如普通用户访问管理员接口），替代 Security 默认的「403 页面响应」，返回自定义 JSON 格式的 “无权限” 提示，适配前后端分离。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 授权异常处理</span></span><br><span class="line"><span class="comment"> * 核心：拦截已登录用户的“权限不足”请求，返回统一JSON格式的“无权限”提示</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span> <span class="comment">// 注入Spring容器，供Security配置使用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomAccessDeniedHandler</span> <span class="keyword">implements</span> <span class="title class_">AccessDeniedHandler</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        <span class="comment">// 1. 自定义响应体：状态码403（HTTP标准“禁止访问”码），提示“无权限”</span></span><br><span class="line">        Result&lt;Object&gt; error = Result.error(<span class="number">403</span>, <span class="string">&quot;无权限访问&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 跨域与响应格式配置：同匿名用户处理，确保前端能解析</span></span><br><span class="line">        response.setHeader(<span class="string">&quot;Access-Control-Allow-Origin&quot;</span>, <span class="string">&quot;*&quot;</span>);</span><br><span class="line">        response.setHeader(<span class="string">&quot;Content-type&quot;</span>, <span class="string">&quot;application/json;charset=UTF-8&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 写入响应：返回JSON</span></span><br><span class="line">        response.getWriter().print(JSONUtil.toJsonStr(error));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、自定义跳过认证注解类和-AOP-实现"><a href="#三、自定义跳过认证注解类和-AOP-实现" class="headerlink" title="三、自定义跳过认证注解类和 AOP 实现"></a>三、自定义跳过认证注解类和 AOP 实现</h2><h3 id="3-1-免认证注解：NoAuth"><a href="#3-1-免认证注解：NoAuth" class="headerlink" title="3.1 免认证注解：NoAuth"></a>3.1 免认证注解：<code>NoAuth</code></h3><h4 id="核心作用-2"><a href="#核心作用-2" class="headerlink" title="核心作用"></a>核心作用</h4><p>定义一个「标记注解」，用于标注<strong>不需要登录就能访问的接口 &#x2F; 控制器</strong>（比如登录接口、注册接口、验证码接口），替代传统的 “在 Security 配置中硬编码白名单路径”，更灵活易维护。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 无需认证token注解</span></span><br><span class="line"><span class="comment"> * 核心：标记接口/控制器，告知Security“该路径免登录访问”</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Documented</span> <span class="comment">// 生成API文档时，显示该注解</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span> <span class="comment">// 注解保留到运行时（必须，因为需要在运行时扫描）</span></span><br><span class="line"><span class="meta">@Target(&#123;ElementType.METHOD, ElementType.TYPE&#125;)</span> <span class="comment">// 注解可用于：方法（单个接口）、类（整个控制器）</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> NoAuth &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-免认证路径收集：RequestMappingCollector"><a href="#3-2-免认证路径收集：RequestMappingCollector" class="headerlink" title="3.2 免认证路径收集：RequestMappingCollector"></a>3.2 免认证路径收集：<code>RequestMappingCollector</code></h3><h4 id="核心作用-3"><a href="#核心作用-3" class="headerlink" title="核心作用"></a>核心作用</h4><p>通过实现 Spring 的 <code>BeanPostProcessor</code> 接口，在项目启动时<strong>自动扫描所有加了 @NoAuth 注解的接口路径</strong>，收集成 “免认证白名单”，供 Security 配置使用（避免手动写死白名单）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 免认证token配置（原注释“无线”应为笔误）</span></span><br><span class="line"><span class="comment"> * 核心：项目启动时扫描<span class="doctag">@NoAuth</span>注解，自动收集免认证路径，生成白名单</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span> <span class="comment">// 注入Spring容器，启动时自动执行BeanPostProcessor逻辑</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestMappingCollector</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存储免认证路径的白名单：用LinkedHashSet保证顺序且去重（避免同一路径重复添加）</span></span><br><span class="line">    <span class="meta">@Getter</span></span><br><span class="line">    <span class="meta">@Setter</span></span><br><span class="line">    <span class="keyword">private</span> Set&lt;String&gt; permitAllUrls = <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">        <span class="comment">// 1. 判断当前Bean是否是“请求映射处理器”（负责管理所有@RequestMapping接口的Bean）</span></span><br><span class="line">        <span class="keyword">if</span> (bean <span class="keyword">instanceof</span> RequestMappingHandlerMapping handlerMapping) &#123;</span><br><span class="line">            <span class="comment">// 2. 获取所有接口的“请求映射信息”（key：映射规则，value：对应的方法）</span></span><br><span class="line">            Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = handlerMapping.getHandlerMethods();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 3. 遍历所有接口，判断是否加了@NoAuth</span></span><br><span class="line">            <span class="keyword">for</span> (Map.Entry&lt;RequestMappingInfo, HandlerMethod&gt; entry : handlerMethods.entrySet()) &#123;</span><br><span class="line">                <span class="type">RequestMappingInfo</span> <span class="variable">info</span> <span class="operator">=</span> entry.getKey(); <span class="comment">// 接口的路径信息（如@RequestMapping(&quot;/login&quot;)）</span></span><br><span class="line">                <span class="type">HandlerMethod</span> <span class="variable">method</span> <span class="operator">=</span> entry.getValue(); <span class="comment">// 接口对应的Controller方法</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 4. 检查：Controller类或方法是否加了@NoAuth注解</span></span><br><span class="line">                <span class="type">boolean</span> <span class="variable">hasNoAuth</span> <span class="operator">=</span> AnnotationUtils.findAnnotation(method.getBeanType(), NoAuth.class) != <span class="literal">null</span> <span class="comment">// 类上的注解</span></span><br><span class="line">                        || AnnotationUtils.findAnnotation(method.getMethod(), NoAuth.class) != <span class="literal">null</span>; <span class="comment">// 方法上的注解</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 5. 若加了@NoAuth，将接口路径加入白名单</span></span><br><span class="line">                <span class="keyword">if</span> (hasNoAuth) &#123;</span><br><span class="line">                    <span class="comment">// 获取接口的所有路径（如@RequestMapping(&#123;&quot;/a&quot;,&quot;/b&quot;&#125;)会返回两个路径）</span></span><br><span class="line">                    permitAllUrls.addAll(info.getPathPatternsCondition().getPatternValues());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 6. BeanPostProcessor接口要求返回原Bean，不修改Bean本身</span></span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、自定义实现登录用户信息：SecurityUser"><a href="#四、自定义实现登录用户信息：SecurityUser" class="headerlink" title="四、自定义实现登录用户信息：SecurityUser"></a>四、自定义实现登录用户信息：<code>SecurityUser</code></h2><h3 id="核心作用-4"><a href="#核心作用-4" class="headerlink" title="核心作用"></a>核心作用</h3><p>继承 Security 提供的 <code>User</code> 类（实现 <code>UserDetails</code> 接口），扩展存储<strong>项目自定义的用户实体（UserEntity）</strong> —— 因为 Security 默认的 <code>User</code> 只包含用户名、密码、权限，无法满足业务需求（比如需要用户 ID、昵称等）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录用户详情</span></span><br><span class="line"><span class="comment"> * 核心：扩展Security的User类，关联项目自定义的UserEntity，存储完整用户信息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="meta">@Setter</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityUser</span> <span class="keyword">extends</span> <span class="title class_">User</span> &#123; <span class="comment">// 继承Security的User，自动实现UserDetails接口</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 扩展字段：关联项目自定义的用户实体（存储用户ID、昵称、角色等业务字段）</span></span><br><span class="line">    <span class="keyword">private</span> UserEntity userEntity;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造方法：调用父类构造（传递Security必需的用户名、密码、权限）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SecurityUser</span><span class="params">(String username, String password, Collection&lt;? extends GrantedAuthority&gt; authorities)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(username, password, authorities);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、自定义实现-UserDetailsService-接口：UserDetailsServiceImpl"><a href="#五、自定义实现-UserDetailsService-接口：UserDetailsServiceImpl" class="headerlink" title="五、自定义实现 UserDetailsService 接口：UserDetailsServiceImpl"></a>五、自定义实现 <code>UserDetailsService</code> 接口：<code>UserDetailsServiceImpl</code></h2><h3 id="核心作用-5"><a href="#核心作用-5" class="headerlink" title="核心作用"></a>核心作用</h3><p>实现 Security 的 <code>UserDetailsService</code> 接口，是<strong>用户认证的核心数据源</strong>—— 当用户登录时，Security 会调用该类的 <code>loadUserByUsername</code> 方法，从数据库查询用户信息，供后续密码校验和权限赋值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 实现 UserDetailsService 接口的用户详情服务类</span></span><br><span class="line"><span class="comment"> * 核心：登录时查询数据库获取用户信息，封装成Security能识别的SecurityUser</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Service</span> <span class="comment">// 注入Spring容器，供Security的AuthenticationManager使用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDetailsServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserDetailsService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入项目的用户业务服务（用于从数据库查用户）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IUserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> SecurityUser <span class="title function_">loadUserByUsername</span><span class="params">(String username)</span> <span class="keyword">throws</span> UsernameNotFoundException &#123;</span><br><span class="line">        <span class="comment">// 1. 根据用户名查询数据库：调用业务层方法，获取自定义UserEntity</span></span><br><span class="line">        <span class="type">UserEntity</span> <span class="variable">userEntity</span> <span class="operator">=</span> userService.findByUsername(username);</span><br><span class="line">        <span class="comment">// 2. 若用户不存在，抛出Security标准异常（会被后续异常处理器捕获，返回“用户不存在”）</span></span><br><span class="line">        <span class="keyword">if</span> (userEntity == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UsernameNotFoundException</span>(<span class="string">&quot;用户不存在：&quot;</span> + username);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 构建用户权限集合：从UserEntity中获取权限编码（如“user:list”），转为Security的GrantedAuthority</span></span><br><span class="line">        Collection&lt;GrantedAuthority&gt; authorities = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotBlank(userEntity.getPermissionsCode())) &#123; <span class="comment">// 若用户有权限编码（非空）</span></span><br><span class="line">            <span class="comment">// 拆分权限编码（假设数据库存储格式为“user:list,user:add”）</span></span><br><span class="line">            <span class="keyword">for</span> (String code : userEntity.getPermissionsCode().split(<span class="string">&quot;,&quot;</span>)) &#123;</span><br><span class="line">                authorities.add(<span class="keyword">new</span> <span class="title class_">SimpleGrantedAuthority</span>(code)); <span class="comment">// 每个编码对应一个权限</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. 封装成SecurityUser：关联UserEntity，传递用户名、密码（数据库存储的加密后密码）、权限</span></span><br><span class="line">        <span class="type">SecurityUser</span> <span class="variable">securityUser</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SecurityUser</span>(userEntity.getUsername(), userEntity.getPassword(), authorities);</span><br><span class="line">        securityUser.setUserEntity(userEntity);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5. 返回SecurityUser：供Security后续校验密码、设置认证信息</span></span><br><span class="line">        <span class="keyword">return</span> securityUser;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、JWT-工具类和-JWT-过滤器实现"><a href="#六、JWT-工具类和-JWT-过滤器实现" class="headerlink" title="六、JWT 工具类和 JWT 过滤器实现"></a>六、JWT 工具类和 JWT 过滤器实现</h2><h3 id="6-1-JWT-工具类：JwtUtils"><a href="#6-1-JWT-工具类：JwtUtils" class="headerlink" title="6.1 JWT 工具类：JwtUtils"></a>6.1 JWT 工具类：<code>JwtUtils</code></h3><h4 id="核心作用-6"><a href="#核心作用-6" class="headerlink" title="核心作用"></a>核心作用</h4><p>封装 JWT 的核心操作：<strong>生成 Token（登录成功后返回给前端）、解析 Token（从请求头提取用户信息）、验证 Token（是否过期、签名是否合法）</strong>，是前后端分离认证的 “核心工具”。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * JWT 工具类，用于生成、解析和验证 JWT token</span></span><br><span class="line"><span class="comment"> * 核心：处理JWT的全生命周期，依赖配置文件中的密钥和过期时间</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span> <span class="comment">// 注入Spring容器，供登录接口和JWT过滤器使用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtils</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从配置文件读取JWT密钥（如application.yml中的jwt.secret）</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.secret&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String jwtSecret;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从配置文件读取JWT过期时间（毫秒，如3600000=1小时）</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.expiration&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> jwtExpirationMs;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 从Token中提取用户名（Subject字段，JWT标准字段）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">extractUsername</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> extractClaim(token, Claims::getSubject);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 从Token中提取过期时间（Expiration字段，JWT标准字段）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Date <span class="title function_">extractExpiration</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> extractClaim(token, Claims::getExpiration);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通用方法：从Token中提取指定的“声明”（Claim，JWT的自定义字段或标准字段）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">extractClaim</span><span class="params">(String token, Function&lt;Claims, T&gt; claimsResolver)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> extractAllClaims(token); <span class="comment">// 先提取所有声明</span></span><br><span class="line">        <span class="keyword">return</span> claimsResolver.apply(claims); <span class="comment">// 根据传入的函数获取指定声明</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 提取Token中的所有声明（包括标准字段和自定义字段）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Claims <span class="title function_">extractAllClaims</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="comment">// JWT解析器构建：设置签名密钥（必须和生成Token时一致，否则解析失败）</span></span><br><span class="line">        <span class="keyword">return</span> Jwts.parserBuilder()</span><br><span class="line">                .setSigningKey(key()) <span class="comment">// 传入密钥（见下方key()方法）</span></span><br><span class="line">                .build()</span><br><span class="line">                .parseClaimsJws(token) <span class="comment">// 解析Token（若签名不合法、格式错误，会抛异常）</span></span><br><span class="line">                .getBody(); <span class="comment">// 获取声明体</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 检查Token是否过期</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Boolean <span class="title function_">isTokenExpired</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="comment">// 比较Token的过期时间和当前时间：若过期时间在当前时间之前，说明已过期</span></span><br><span class="line">        <span class="keyword">return</span> extractExpiration(token).before(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成JWT Token（登录成功后调用）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> userDetails：Security的用户详情（包含用户名、权限等）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateToken</span><span class="params">(UserDetails userDetails)</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; claims = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(); <span class="comment">// 自定义声明（可添加用户ID、角色等）</span></span><br><span class="line">        <span class="keyword">return</span> createToken(claims, userDetails.getUsername()); <span class="comment">// 传入自定义声明和用户名（Subject）</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成签名密钥：将配置文件中的字符串密钥转为JWT要求的SecretKey（Base64解码）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> SecretKey <span class="title function_">key</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">byte</span>[] keyBytes = Decoders.BASE64.decode(jwtSecret); <span class="comment">// 解码Base64格式的密钥</span></span><br><span class="line">        <span class="keyword">return</span> Keys.hmacShaKeyFor(keyBytes); <span class="comment">// 生成HS256算法的密钥（和生成Token时的签名算法一致）</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 实际创建Token的方法（封装JWT标准字段）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">createToken</span><span class="params">(Map&lt;String, Object&gt; claims, String subject)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">                .setClaims(claims) <span class="comment">// 设置自定义声明</span></span><br><span class="line">                .setSubject(subject) <span class="comment">// 设置用户名（Subject，JWT标准字段）</span></span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis())) <span class="comment">// 设置签发时间（标准字段）</span></span><br><span class="line">                .setExpiration(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + jwtExpirationMs)) <span class="comment">// 设置过期时间</span></span><br><span class="line">                .signWith(key(), SignatureAlgorithm.HS256) <span class="comment">// 设置签名算法和密钥</span></span><br><span class="line">                .compact(); <span class="comment">// 生成Token字符串</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 验证Token合法性：1. 用户名匹配 2. Token未过期</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Boolean <span class="title function_">validateToken</span><span class="params">(String token, UserDetails userDetails)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> extractUsername(token); <span class="comment">// 从Token提取用户名</span></span><br><span class="line">        <span class="comment">// 验证：用户名和userDetails中的一致 + Token未过期</span></span><br><span class="line">        <span class="keyword">return</span> (username.equals(userDetails.getUsername()) &amp;&amp; !isTokenExpired(token));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 从请求头中解析Token：前端需将Token放在Authorization头，格式为“Bearer &#123;token&#125;”</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">parseJwt</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">headerAuth</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>); <span class="comment">// 获取Authorization头</span></span><br><span class="line">        <span class="comment">// 检查头信息是否符合“Bearer ”前缀（前后端约定的格式）</span></span><br><span class="line">        <span class="keyword">if</span> (headerAuth != <span class="literal">null</span> &amp;&amp; headerAuth.startsWith(<span class="string">&quot;Bearer &quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> headerAuth.substring(<span class="number">7</span>); <span class="comment">// 截取“Bearer ”后的Token字符串（7是“Bearer ”的长度）</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 无Token或格式错误，返回null</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-JWT-过滤器：JwtAuthenticationFilter"><a href="#6-2-JWT-过滤器：JwtAuthenticationFilter" class="headerlink" title="6.2 JWT 过滤器：JwtAuthenticationFilter"></a>6.2 JWT 过滤器：<code>JwtAuthenticationFilter</code></h3><h4 id="核心作用-7"><a href="#核心作用-7" class="headerlink" title="核心作用"></a>核心作用</h4><p>继承 Security 的 <code>OncePerRequestFilter</code>（确保每次请求只执行一次），<strong>拦截所有请求</strong>，从请求头提取 JWT Token，验证合法性后将用户信息存入 <code>SecurityContext</code>（让 Security 后续能识别 “当前用户已登录”）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * JWT 认证过滤器，用于拦截请求并验证 JWT token</span></span><br><span class="line"><span class="comment"> * 核心：每次请求前验证Token，合法则设置认证信息，让Security识别已登录用户</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtAuthenticationFilter</span> <span class="keyword">extends</span> <span class="title class_">OncePerRequestFilter</span> &#123; <span class="comment">// 确保单次请求只执行一次过滤</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入JWT工具类（解析、验证Token）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> JwtUtils jwtUtils;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入UserDetailsService（验证Token时需查询用户信息）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> UserDetailsService userDetailsService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doFilterInternal</span><span class="params">(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1. 从请求头解析Token</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> jwtUtils.parseJwt(request);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 2. 若Token不为null（存在Token）</span></span><br><span class="line">            <span class="keyword">if</span> (Objects.nonNull(jwt)) &#123;</span><br><span class="line">                <span class="comment">// 3. 从Token提取用户名</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> jwtUtils.extractUsername(jwt);</span><br><span class="line">                <span class="comment">// 4. 从数据库查询用户信息（封装成UserDetails）</span></span><br><span class="line">                <span class="type">UserDetails</span> <span class="variable">userDetails</span> <span class="operator">=</span> userDetailsService.loadUserByUsername(username);</span><br><span class="line">                <span class="comment">// 5. 验证Token合法性（用户名匹配 + 未过期）</span></span><br><span class="line">                <span class="keyword">if</span> (jwtUtils.validateToken(jwt, userDetails)) &#123;</span><br><span class="line">                    <span class="comment">// 6. 构建认证信息：Security的UsernamePasswordAuthenticationToken（存储用户信息和权限）</span></span><br><span class="line">                    <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">authentication</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(</span><br><span class="line">                            userDetails, <span class="literal">null</span>, userDetails.getAuthorities()); <span class="comment">// 密码设为null（已通过Token验证，无需密码）</span></span><br><span class="line">                    <span class="comment">// 设置请求详情（如IP、会话ID，可选但建议加）</span></span><br><span class="line">                    authentication.setDetails(<span class="keyword">new</span> <span class="title class_">WebAuthenticationDetailsSource</span>().buildDetails(request));</span><br><span class="line">                    <span class="comment">// 7. 将认证信息存入SecurityContext：后续Security会从这里获取当前用户信息</span></span><br><span class="line">                    SecurityContextHolder.getContext().setAuthentication(authentication);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 8. 捕获异常（如Token无效、过期）：打印日志，不中断过滤器链（让后续流程处理未认证情况）</span></span><br><span class="line">            logger.error(<span class="string">&quot;Cannot set user authentication: &#123;&#125;&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 9. 继续执行过滤器链：无论是否有认证信息，都让请求进入下一个过滤器（如授权过滤器）</span></span><br><span class="line">        filterChain.doFilter(request, response);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、Spring-Security-配置：SecurityConfig"><a href="#七、Spring-Security-配置：SecurityConfig" class="headerlink" title="七、Spring Security 配置：SecurityConfig"></a>七、Spring Security 配置：<code>SecurityConfig</code></h2><h3 id="核心作用-8"><a href="#核心作用-8" class="headerlink" title="核心作用"></a>核心作用</h3><p>Security 的<strong>核心配置类</strong>：整合上述所有自定义组件（过滤器、异常处理器、白名单），配置认证授权规则（哪些路径免认证、哪些需权限）、跨域、会话管理等，是整个 Security 流程的 “总指挥”。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Spring Security 配置类</span></span><br><span class="line"><span class="comment"> * 核心：整合所有自定义组件，定义认证授权规则，配置Security核心流程</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Configuration</span> <span class="comment">// 标记为配置类</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span> <span class="comment">// 启用Spring Security功能</span></span><br><span class="line"><span class="meta">@EnableMethodSecurity</span> <span class="comment">// 启用方法级别的权限控制（如@PreAuthorize(&quot;hasRole(&#x27;ADMIN&#x27;)&quot;)）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入免认证路径收集器（存储@NoAuth标注的路径）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RequestMappingCollector requestMappingCollector;</span><br><span class="line">    <span class="comment">// 注入匿名用户异常处理器</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> AnonymousAuthenticationEntryPoint anonymousAuthenticationEntryPoint;</span><br><span class="line">    <span class="comment">// 注入权限不足异常处理器</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> CustomAccessDeniedHandler customAccessDeniedHandler;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 自定义权限服务（假设项目需要自定义权限表达式，如<span class="doctag">@PreAuthorize</span>(&quot;<span class="doctag">@pms</span>.hasPerm(&#x27;user:list&#x27;)&quot;)）</span></span><br><span class="line"><span class="comment">     * 核心：提供自定义权限校验逻辑，供方法级注解使用</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean(&quot;pms&quot;)</span> <span class="comment">// 命名为pms，对应注解中的@pms</span></span><br><span class="line">    <span class="keyword">public</span> PermissionService <span class="title function_">permissionService</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PermissionService</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 支持自定义权限表达式（配合<span class="doctag">@EnableMethodSecurity</span>）</span></span><br><span class="line"><span class="comment">     * 核心：让Security识别自定义的权限服务（如<span class="doctag">@pms</span>）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AnnotationTemplateExpressionDefaults <span class="title function_">prePostTemplateDefaults</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AnnotationTemplateExpressionDefaults</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 注册JWT过滤器到Spring容器</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> JwtAuthenticationFilter <span class="title function_">jwtAuthenticationFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JwtAuthenticationFilter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 跨域配置（前后端分离必配）</span></span><br><span class="line"><span class="comment">     * 核心：定义允许的跨域规则（域名、方法、请求头），替代之前处理器中的硬编码跨域头</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CorsConfigurationSource <span class="title function_">corsConfigurationSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">CorsConfiguration</span> <span class="variable">configuration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CorsConfiguration</span>();</span><br><span class="line">        configuration.setAllowedOriginPatterns(List.of(<span class="string">&quot;*&quot;</span>)); <span class="comment">// 允许所有域名（生产环境建议指定具体域名，如&quot;http://localhost:8080&quot;）</span></span><br><span class="line">        configuration.setAllowedMethods(List.of(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;POST&quot;</span>, <span class="string">&quot;PUT&quot;</span>, <span class="string">&quot;DELETE&quot;</span>, <span class="string">&quot;OPTIONS&quot;</span>, <span class="string">&quot;PATCH&quot;</span>)); <span class="comment">// 允许的HTTP方法</span></span><br><span class="line">        configuration.setAllowedHeaders(List.of(<span class="string">&quot;*&quot;</span>)); <span class="comment">// 允许的请求头（如Authorization、Content-Type）</span></span><br><span class="line">        configuration.setAllowCredentials(<span class="literal">true</span>); <span class="comment">// 允许携带Cookie（若前端需要传Cookie，需开启）</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 注册跨域规则：所有路径都适用</span></span><br><span class="line">        <span class="type">UrlBasedCorsConfigurationSource</span> <span class="variable">source</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UrlBasedCorsConfigurationSource</span>();</span><br><span class="line">        source.registerCorsConfiguration(<span class="string">&quot;/**&quot;</span>, configuration);</span><br><span class="line">        <span class="keyword">return</span> source;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 核心配置：定义Security的过滤器链（认证授权流程）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http</span><br><span class="line">                <span class="comment">// 1. 配置跨域：使用上述corsConfigurationSource</span></span><br><span class="line">                .cors(cors -&gt; cors.configurationSource(corsConfigurationSource()))</span><br><span class="line">                <span class="comment">// 2. 禁用CSRF：前后端分离项目中，CSRF令牌难以传递，且JWT本身已防篡改，故禁用</span></span><br><span class="line">                .csrf(AbstractHttpConfigurer::disable)</span><br><span class="line">                <span class="comment">// 3. 会话管理：设置为无状态（STATELESS），因为JWT是无状态认证，不依赖Session</span></span><br><span class="line">                .sessionManagement(se -&gt; se.sessionCreationPolicy(SessionCreationPolicy.STATELESS))</span><br><span class="line">                <span class="comment">// 4. 授权规则配置</span></span><br><span class="line">                .authorizeHttpRequests(auth -&gt; auth</span><br><span class="line">                        <span class="comment">// 4.1 免认证路径：从requestMappingCollector获取@NoAuth标注的路径，允许匿名访问</span></span><br><span class="line">                        .requestMatchers(requestMappingCollector.getPermitAllUrls().toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]))</span><br><span class="line">                        .permitAll()</span><br><span class="line">                        <span class="comment">// 4.2 其他所有路径：必须认证（已登录）才能访问</span></span><br><span class="line">                        .anyRequest()</span><br><span class="line">                        .authenticated())</span><br><span class="line">                <span class="comment">// 5. 禁用默认登录页面：前后端分离用自定义登录接口（/login），故禁用Security默认表单登录</span></span><br><span class="line">                .formLogin(AbstractHttpConfigurer::disable)</span><br><span class="line">                <span class="comment">// 6. 禁用默认退出：后续可自定义退出接口（如/logout，将Token加入黑名单），故禁用默认退出</span></span><br><span class="line">                .logout(AbstractHttpConfigurer::disable)</span><br><span class="line">                <span class="comment">// 7. 异常处理：配置匿名用户和权限不足的异常处理器</span></span><br><span class="line">                .exceptionHandling(exception -&gt; &#123;</span><br><span class="line">                    exception.authenticationEntryPoint(anonymousAuthenticationEntryPoint); <span class="comment">// 未登录异常</span></span><br><span class="line">                    exception.accessDeniedHandler(customAccessDeniedHandler); <span class="comment">// 权限不足异常</span></span><br><span class="line">                &#125;)</span><br><span class="line">                <span class="comment">// 8. 添加JWT过滤器：将JWT过滤器放在UsernamePasswordAuthenticationFilter之前（先验证Token，再执行后续认证）</span></span><br><span class="line">                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 返回配置好的过滤器链</span></span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 密码编码器：使用BCrypt算法加密密码（Security推荐，不可逆，带盐值）</span></span><br><span class="line"><span class="comment">     * 核心：登录时Security会自动用该编码器校验密码（数据库存储BCrypt加密后的密码）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 注册认证管理器：供登录接口使用（调用authenticate方法校验用户名密码）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationManager <span class="title function_">authenticationManager</span><span class="params">(AuthenticationConfiguration authenticationConfiguration)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> authenticationConfiguration.getAuthenticationManager();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、登录实现：AuthController"><a href="#八、登录实现：AuthController" class="headerlink" title="八、登录实现：AuthController"></a>八、登录实现：<code>AuthController</code></h2><h3 id="核心作用-9"><a href="#核心作用-9" class="headerlink" title="核心作用"></a>核心作用</h3><p>提供<strong>自定义登录接口</strong>（<code>/login</code>）和 Token 校验接口（<code>/check</code>），是用户触发认证流程的入口：接收前端传入的用户名密码，调用 Security 的 <code>AuthenticationManager</code> 校验，成功后生成 JWT 返回给前端。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录控制器</span></span><br><span class="line"><span class="comment"> * 核心：提供自定义登录接口和Token校验接口，对接前端认证需求</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span> <span class="comment">// 标记为REST接口控制器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入认证管理器（用于校验用户名密码）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenticationManager;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入JWT工具类（生成Token）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> JwtUtils jwtUtils;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入用户业务服务（可选，视业务需求）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> IUserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注入HttpServletRequest（用于从请求头提取Token，供/check接口使用）</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> HttpServletRequest request;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户登录接口（核心）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@NoAuth</span>：标记为免认证路径（未登录用户也能访问）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@RequestBody</span>：接收前端传入的JSON格式登录参数（用户名、密码）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@NoAuth</span></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;JwtResponse&gt; <span class="title function_">login</span><span class="params">(<span class="meta">@RequestBody</span> LoginRequest loginRequest)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 调用AuthenticationManager校验用户名密码：</span></span><br><span class="line">        <span class="comment">// 传入UsernamePasswordAuthenticationToken（封装用户名密码），内部会调用UserDetailsService查用户，并用PasswordEncoder校验密码</span></span><br><span class="line">        <span class="type">Authentication</span> <span class="variable">authentication</span> <span class="operator">=</span> authenticationManager.authenticate(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(loginRequest.username(), loginRequest.password()));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 校验成功：将认证信息存入SecurityContext（供后续流程使用，如JWT过滤器）</span></span><br><span class="line">        SecurityContextHolder.getContext().setAuthentication(authentication);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 获取当前登录用户的详情（SecurityUser，包含自定义的UserEntity）</span></span><br><span class="line">        <span class="type">SecurityUser</span> <span class="variable">userDetails</span> <span class="operator">=</span> (SecurityUser) authentication.getPrincipal();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 生成JWT Token（传入SecurityUser，包含用户名和权限）</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> jwtUtils.generateToken(userDetails);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. 敏感信息处理：清空UserEntity中的密码（避免返回给前端）</span></span><br><span class="line">        userDetails.getUserEntity().setPassword(<span class="string">&quot;只有聪明的人才能看到密码&quot;</span>); <span class="comment">// 占位符，实际可设为null</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 6. 返回登录结果：包含用户信息、角色、Token（用自定义JwtResponse封装）</span></span><br><span class="line">        <span class="keyword">return</span> Result.success(<span class="keyword">new</span> <span class="title class_">JwtResponse</span>(userDetails.getUserEntity(), userDetails.getUserEntity().getRole(), jwt));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Token校验接口（可选）</span></span><br><span class="line"><span class="comment">     * 作用：前端可定期调用该接口，检查Token是否有效/过期</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/check&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;String&gt; <span class="title function_">check</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1. 从请求头提取Token</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> jwtUtils.parseJwt(request);</span><br><span class="line">            <span class="comment">// 2. 检查Token是否过期</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">tokenExpired</span> <span class="operator">=</span> jwtUtils.isTokenExpired(token);</span><br><span class="line">            <span class="comment">// 3. 返回结果：过期则提示“token已过期”，否则返回成功</span></span><br><span class="line">            <span class="keyword">return</span> tokenExpired ? Result.error(<span class="number">424</span>, <span class="string">&quot;token已过期&quot;</span>) : Result.success();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception ignored) &#123;</span><br><span class="line">            <span class="comment">// 4. 捕获异常（如Token无效、格式错误）：返回“token无效”</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Result.error(<span class="number">424</span>, <span class="string">&quot;token 无效&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 登录请求体（使用Java Record，简洁替代POJO）</span></span><br><span class="line"><span class="comment">     * 核心：封装前端传入的登录参数（用户名、密码），实现Serializable便于序列化</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">LoginRequest</span><span class="params">(String username, String password)</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 登录响应体（使用Java Record）</span></span><br><span class="line"><span class="comment">     * 核心：封装登录成功后返回给前端的数据（用户信息、角色、Token）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">JwtResponse</span><span class="params">(UserEntity user, RoleEntity role, String token)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="整体流程总结"><a href="#整体流程总结" class="headerlink" title="整体流程总结"></a>整体流程总结</h2><ol><li><strong>未登录访问需认证接口</strong>：JWT 过滤器未提取到有效 Token → Security 触发 <code>AnonymousAuthenticationEntryPoint</code> → 返回 “未登录” JSON。</li><li><strong>已登录但权限不足</strong>：JWT 验证通过，但用户权限不匹配 → Security 触发 <code>CustomAccessDeniedHandler</code> → 返回 “无权限” JSON。</li><li><strong>登录流程</strong>：前端调用 <code>/login</code>（@NoAuth 免认证）→ 传入用户名密码 → <code>AuthenticationManager</code> 校验 → 成功生成 JWT → 返回给前端。</li><li><strong>已登录访问接口</strong>：前端在 Authorization 头携带 JWT → JWT 过滤器解析验证 Token → 合法则设置认证信息 → Security 允许访问接口。</li></ol>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/52e4454/</id>
    <link href="https://luoyuanxiang.top/posts/52e4454/"/>
    <published>2025-10-11T20:58:57.000Z</published>
    <summary>详细讲解基于Spring Security和JWT的前后端分离认证方案，包括匿名用户处理、权限控制、免认证注解、JWT工具类、过滤器配置和登录接口实现等核心组件。</summary>
    <title>Spring Security JWT认证完整实现</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="生活随笔" scheme="https://luoyuanxiang.top/categories/%E7%94%9F%E6%B4%BB%E9%9A%8F%E7%AC%94/"/>
    <category term="docker" scheme="https://luoyuanxiang.top/tags/docker/"/>
    <category term="GitHub" scheme="https://luoyuanxiang.top/tags/GitHub/"/>
    <category term="actions" scheme="https://luoyuanxiang.top/tags/actions/"/>
    <content>
      <![CDATA[<h1 id="使用-GitHub-Actions-实现-Spring-Boot-项目打包并推送至阿里云镜像仓库"><a href="#使用-GitHub-Actions-实现-Spring-Boot-项目打包并推送至阿里云镜像仓库" class="headerlink" title="使用 GitHub Actions 实现 Spring Boot 项目打包并推送至阿里云镜像仓库"></a>使用 GitHub Actions 实现 Spring Boot 项目打包并推送至阿里云镜像仓库</h1><h2 id="GitHub-Actions-配置文件"><a href="#GitHub-Actions-配置文件" class="headerlink" title="GitHub Actions 配置文件"></a>GitHub Actions 配置文件</h2><p>在项目根目录下创建 <code>.github/workflows/docker-publish.yml</code> 文件，内容如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 此工作流用于构建 Spring Boot 项目并推送 Docker 镜像至阿里云容器镜像服务</span></span><br><span class="line"><span class="comment"># 更多信息请参考：https://docs.github.com/en/actions</span></span><br><span class="line"></span><br><span class="line"><span class="attr">name:</span> <span class="string">构建并推送至阿里云容器镜像</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 触发条件：推送到 master 分支或创建 v 开头的标签时触发，同时监控 master 分支的拉取请求</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span> [ <span class="string">master</span> ]</span><br><span class="line">    <span class="attr">tags:</span> [ <span class="string">&#x27;v*&#x27;</span> ]</span><br><span class="line">  <span class="attr">pull_request:</span></span><br><span class="line">    <span class="attr">branches:</span> [ <span class="string">master</span> ]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">检出代码</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v3</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">登录阿里云容器镜像服务</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">aliyun/acr-login@v1</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">login-server:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.ALIYUN_ACR_REGISTRY</span> <span class="string">&#125;&#125;</span>  <span class="comment"># 示例：registry.cn-beijing.aliyuncs.com</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.ALIYUN_ACR_USERNAME</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.ALIYUN_ACR_PASSWORD</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">构建并推送</span> <span class="string">Docker</span> <span class="string">镜像</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">ACR_REGISTRY:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.ALIYUN_ACR_REGISTRY</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">ACR_NAMESPACE:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.ALIYUN_ACR_NAMESPACE</span> <span class="string">&#125;&#125;</span>  <span class="comment"># 命名空间</span></span><br><span class="line">          <span class="attr">DOCKER_VERSION:</span> <span class="string">latest</span></span><br><span class="line">          <span class="attr">IMAGE_NAME:</span> <span class="string">thrive-blog</span>  <span class="comment"># 镜像名称</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          # 构建镜像</span></span><br><span class="line"><span class="string">          docker build -t $ACR_REGISTRY/$ACR_NAMESPACE/$IMAGE_NAME:$DOCKER_VERSION .</span></span><br><span class="line"><span class="string"></span>          </span><br><span class="line">          <span class="comment"># 推送镜像至仓库</span></span><br><span class="line">          <span class="string">docker</span> <span class="string">push</span> <span class="string">$ACR_REGISTRY/$ACR_NAMESPACE/$IMAGE_NAME:$DOCKER_VERSION</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">连接服务器并部署应用</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">appleboy/ssh-action@v1</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">host:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.REMOTE_HOST</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.REMOTE_USER</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.SSH_PRIVATE_KEY</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">22</span></span><br><span class="line">          <span class="attr">script:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            echo &quot;开始执行服务器部署命令&quot;</span></span><br><span class="line"><span class="string">            # 停止并移除旧容器</span></span><br><span class="line"><span class="string">            docker stop thrive-blog || true</span></span><br><span class="line"><span class="string">            docker rm thrive-blog || true</span></span><br><span class="line"><span class="string"></span>            </span><br><span class="line">            <span class="comment"># 清理旧镜像并拉取最新镜像</span></span><br><span class="line">            <span class="string">docker</span> <span class="string">rmi</span> <span class="string">registry.cn-chengdu.aliyuncs.com/thrivex-blog/thrive-blog:latest</span> <span class="string">||</span> <span class="literal">true</span></span><br><span class="line">            <span class="string">docker</span> <span class="string">pull</span> <span class="string">registry.cn-chengdu.aliyuncs.com/thrivex-blog/thrive-blog:latest</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 使用 Docker Compose 重新部署应用</span></span><br><span class="line">            <span class="string">cd</span> <span class="string">/root/thrive-blog</span></span><br><span class="line">            <span class="string">docker-compose</span> <span class="string">up</span> <span class="string">-d</span></span><br><span class="line">            <span class="string">echo</span> <span class="string">&quot;服务器部署完成&quot;</span></span><br></pre></td></tr></table></figure><blockquote><p>[!NOTE] 配置说明</p><ul><li><strong>分支配置</strong>：请根据实际项目需求调整监控的分支名称</li><li><strong>ALIYUN_ACR_REGISTRY</strong>：阿里云容器镜像服务地址，例如：registry.cn-beijing.aliyuncs.com</li><li><strong>ALIYUN_ACR_USERNAME</strong>：阿里云账号用户名</li><li><strong>ALIYUN_ACR_PASSWORD</strong>：阿里云账号密码</li><li><strong>ALIYUN_ACR_NAMESPACE</strong>：容器镜像服务的命名空间</li><li><strong>REMOTE_HOST</strong>：服务器 IP 地址</li><li><strong>REMOTE_USER</strong>：服务器用户名</li><li><strong>SSH_PRIVATE_KEY</strong>：服务器 SSH 私钥</li></ul></blockquote><h2 id="Dockerfile-配置文件"><a href="#Dockerfile-配置文件" class="headerlink" title="Dockerfile 配置文件"></a>Dockerfile 配置文件</h2><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用 Maven 和 JDK 8 作为构建环境</span></span><br><span class="line"><span class="keyword">FROM</span> maven:<span class="number">3.6</span>.<span class="number">3</span>-openjdk-<span class="number">8</span> AS builder</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置工作目录</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制项目文件</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行 Maven 构建，跳过测试阶段以提高构建效率</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> mvn install -Dmaven.test.skip=<span class="literal">true</span> -P pro</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用轻量级 JRE 环境作为运行时镜像</span></span><br><span class="line"><span class="keyword">FROM</span> openjdk:<span class="number">8</span>-jre-alpine</span><br><span class="line"></span><br><span class="line"><span class="comment"># 维护者信息</span></span><br><span class="line"><span class="keyword">LABEL</span><span class="language-bash"> maintainer=<span class="string">&quot;1141306760@qq.com&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置时区为上海</span></span><br><span class="line"><span class="keyword">ENV</span> TZ=Asia/Shanghai</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">ln</span> -snf /usr/share/zoneinfo/<span class="variable">$TZ</span> /etc/localtime &amp;&amp; <span class="built_in">echo</span> <span class="variable">$TZ</span> &gt; /etc/timezone</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置工作目录</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 从构建阶段复制生成的 Jar 文件</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /app/blog/target/*.jar app.jar</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 声明应用端口</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">9003</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 exec 格式启动应用，确保正确接收系统信号</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><blockquote><p>[!TIP] 提示<br>Dockerfile 中已包含完整的项目构建配置，可与 GitHub Actions 工作流无缝配合，实现从代码到镜像的全自动打包和部署流程。</p></blockquote>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/b52cfec1/</id>
    <link href="https://luoyuanxiang.top/posts/b52cfec1/"/>
    <published>2025-09-05T15:26:34.000Z</published>
    <summary>本文详细介绍了如何通过GitHub Actions工作流实现Spring Boot项目的自动化构建与部署，包括Docker镜像打包及推送至阿里云容器镜像服务的完整配置流程和注意事项。</summary>
    <title>GitHub Actions自动化部署Spring Boot至阿里云镜像仓库</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <category term="线程池" scheme="https://luoyuanxiang.top/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
    <category term="CPU" scheme="https://luoyuanxiang.top/tags/CPU/"/>
    <category term="线程" scheme="https://luoyuanxiang.top/tags/%E7%BA%BF%E7%A8%8B/"/>
    <content>
      <![CDATA[<h1 id="线程池配置优化指南"><a href="#线程池配置优化指南" class="headerlink" title="线程池配置优化指南"></a>线程池配置优化指南</h1><h2 id="线程运行机制"><a href="#线程运行机制" class="headerlink" title="线程运行机制"></a>线程运行机制</h2><p><img src="https://cdn.luoyuanxiang.top/img/cd69692de94f79a594b2bf1619a0ed09.jpeg"></p><h2 id="线程池参数配置策略"><a href="#线程池参数配置策略" class="headerlink" title="线程池参数配置策略"></a>线程池参数配置策略</h2><p>手动配置线程池时，需要合理设置最大线程数和核心线程数。业界普遍建议根据服务器CPU特性进行配置：</p><ol><li><p><strong>获取CPU核数</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(Runtime.getRuntime().availableProcessors());</span><br></pre></td></tr></table></figure><p>CPU核数 &#x3D; Runtime.getRuntime().availableProcessors()</p></li><li><p><strong>判断任务类型</strong>：</p><ul><li><p><strong>IO密集型</strong>：涉及大量网络、文件操作</p><ul><li>常规设置：核心线程数 &#x3D; CPU核数 × 2</li><li>优化方案（大厂实践）：核心线程数 &#x3D; CPU核数 &#x2F; (1 - 阻塞系数)<br>例如阻塞系数0.8，4核CPU：核心线程数 &#x3D; 4 &#x2F; (1-0.8) &#x3D; 20</li></ul></li><li><p><strong>CPU密集型</strong>：以计算为主，CPU使用率接近100%</p><ul><li>推荐设置：核心线程数 &#x3D; CPU核数 + 1</li></ul></li></ul></li></ol><p><img src="https://cdn.luoyuanxiang.top/img/1725862154539-4564f526-7980-46d0-9a33-4fa83f9e23d6.jpeg"></p><h2 id="最大线程数（maxPoolSize）配置"><a href="#最大线程数（maxPoolSize）配置" class="headerlink" title="最大线程数（maxPoolSize）配置"></a>最大线程数（maxPoolSize）配置</h2><p>当系统负载达到峰值，核心线程无法及时处理所有任务时，需要增加线程数量。例如：</p><ul><li>每秒200个任务需要20个线程</li><li>当任务量增至每秒1000个时，所需线程数 &#x3D; (1000 - 队列容量) × (20&#x2F;200) &#x3D; 60<br>建议将maxPoolSize设置为60，也可采用CPU核数×2或×4的经验值</li></ul><h2 id="线程空闲时间（keepAliveTime）设置"><a href="#线程空闲时间（keepAliveTime）设置" class="headerlink" title="线程空闲时间（keepAliveTime）设置"></a>线程空闲时间（keepAliveTime）设置</h2><p>为避免线程只增不减，当负载降低时，空闲时间超过keepAliveTime的线程会被自动回收。默认情况下，线程池至少会保持corePoolSize个线程。</p><h2 id="核心线程超时（allowCoreThreadTimeout）"><a href="#核心线程超时（allowCoreThreadTimeout）" class="headerlink" title="核心线程超时（allowCoreThreadTimeout）"></a>核心线程超时（allowCoreThreadTimeout）</h2><p>默认情况下核心线程不会自动退出。通过设置allowCoreThreadTimeout为true，可让核心线程在空闲时也退出。</p><h2 id="队列容量（queueCapacity）规划"><a href="#队列容量（queueCapacity）规划" class="headerlink" title="队列容量（queueCapacity）规划"></a>队列容量（queueCapacity）规划</h2><p>任务队列长度需结合核心线程数和系统响应时间要求：</p><ul><li>计算公式：(核心线程数&#x2F;单个任务处理时间) × 响应时间</li><li>示例：(20&#x2F;0.1) × 2 &#x3D; 400（队列长度可设为400）</li></ul><p><strong>注意</strong>：避免将队列长度设置为Integer.MAX_VALUE（如使用无参LinkedBlockingQueue构造函数），这会导致线程数永远不超过corePoolSize，无法应对突发流量，造成响应时间急剧增加。</p>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/ed5eacc9/</id>
    <link href="https://luoyuanxiang.top/posts/ed5eacc9/"/>
    <published>2025-09-05T09:30:00.000Z</published>
    <summary>本文详细讲解如何根据CPU核数、任务类型（CPU密集型/IO密集型）合理设置线程池的核心线程数、最大线程数、队列容量等关键参数，提供具体计算公式和实践建议，帮助开发者优化线程池性能。</summary>
    <title>线程池参数设置最佳实践</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="docker" scheme="https://luoyuanxiang.top/tags/docker/"/>
    <category term="docker-compose" scheme="https://luoyuanxiang.top/tags/docker-compose/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h3 id="Compose配置文件结构"><a href="#Compose配置文件结构" class="headerlink" title="Compose配置文件结构"></a>Compose配置文件结构</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:alpine</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;6379&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">frontend</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="attr">parallelism:</span> <span class="number">2</span></span><br><span class="line">        <span class="attr">delay:</span> <span class="string">10s</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">on-failure</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:9.4</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db-data:/var/lib/postgresql/data</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">backend</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">placement:</span></span><br><span class="line">        <span class="attr">constraints:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&quot;node.role==manager&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">vote:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">dockersamples/examplevotingapp_vote:before</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5000:80&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">frontend</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">redis</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="attr">parallelism:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">on-failure</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">result:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">dockersamples/examplevotingapp_result:before</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5001:80&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">backend</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="attr">parallelism:</span> <span class="number">2</span></span><br><span class="line">        <span class="attr">delay:</span> <span class="string">10s</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">on-failure</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">worker:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">dockersamples/examplevotingapp_worker</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">frontend</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">backend</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">mode:</span> <span class="string">replicated</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">labels:</span> [<span class="string">APP=VOTING</span>]</span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">on-failure</span></span><br><span class="line">        <span class="attr">delay:</span> <span class="string">10s</span></span><br><span class="line">        <span class="attr">max_attempts:</span> <span class="number">3</span></span><br><span class="line">        <span class="attr">window:</span> <span class="string">120s</span></span><br><span class="line">      <span class="attr">placement:</span></span><br><span class="line">        <span class="attr">constraints:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&quot;node.role==manager&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">visualizer:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">dockersamples/visualizer:stable</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:8080&quot;</span></span><br><span class="line">    <span class="attr">stop_grace_period:</span> <span class="string">1m30s</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/var/run/docker.sock:/var/run/docker.sock&quot;</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">placement:</span></span><br><span class="line">        <span class="attr">constraints:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&quot;node.role==manager&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">frontend:</span></span><br><span class="line">  <span class="attr">backend:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">db-data:</span></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>顶层的<code>version</code>、<code>services</code>、<code>networks</code>和<code>volumes</code>将<code>Compose</code>配置文件分为四个部分，其中<code>version</code>指定<code>Compose</code>配置文件的版本，<code>services</code>定义服务，<code>networks</code>定义网络，<code>volumes</code>定义数据卷。</p><h3 id="服务配置"><a href="#服务配置" class="headerlink" title="服务配置"></a>服务配置</h3><p>服务定义了该服务启动的每个容器的配置，就像将命令行参数传递给docker run一样。比如以下配置：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>services下的redis是用户自定义的服务名称，redis下的image只是众多服务配置项中的其中一个，意思是指定镜像名称或id。下面就对服务的相关配置项进行一个总结。</p><h4 id="1-build"><a href="#1-build" class="headerlink" title="1. build"></a>1. build</h4><p>在构建时应用的配置项。一般直接指定Dockerfile所在文件夹路径，可以是绝对路径，或者相对于Compose配置文件的路径。可以指定为包含构建上下文（context）路径的字符串。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">webapp:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">./dir</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>也可以使用context指定上下文路径，使用dockerfile基于上下文路径指定Dockerfile文件，使用args指定构建参数。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">webapp:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">./dir</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">Dockerfile-alternate</span></span><br><span class="line">      <span class="attr">args:</span></span><br><span class="line">        <span class="attr">buildno:</span> <span class="number">1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果同时指定了build和image。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span> <span class="string">./dir</span></span><br><span class="line"><span class="attr">image:</span> <span class="string">webapp:tag</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Compose会在.&#x2F;dir目录下构建一个名为webapp，标签为tag的镜像。</p><p>使用docker stack deploy时的注意事项：在swarm mode下部署堆栈时，build配置项被忽略。因为docker stack命令不会在部署之前构建镜像。</p><h5 id="1-context"><a href="#1-context" class="headerlink" title="(1)context"></a>(1)context</h5><p>指定包含Dockerfile的目录路径或git仓库url。该目录是发送给Docker守护进程（Daemon）的构建上下文（context）。当配置的值是相对路径时，它将被解释为相对于Compose配置文件的路径。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">./dir</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>指定上下文为Compose配置文件目录下的dir目录。也可以这样写：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span> <span class="string">dir</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="2-dockerfile"><a href="#2-dockerfile" class="headerlink" title="(2)dockerfile"></a>(2)dockerfile</h5><p>指定Dockerfile文件。Compose会使用指定的Dockerfile文件构建镜像，但必须要指定构建上下文路径。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">dockerfile:</span> <span class="string">Dockerfile-alternate</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Compose会使用Compose配置文件所在目录下名为Dockerfile-alternate的Dockerfile文件构建镜像。</p><h5 id="3-args"><a href="#3-args" class="headerlink" title="(3)args"></a>(3)args</h5><p>添加构建参数，这些只能在构建过程中访问的环境变量。首先在Dockerfile文件中指定参数：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ARG</span> buildno</span><br><span class="line"><span class="keyword">ARG</span> gitcommithash</span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&quot;Build number: <span class="variable">$buildno</span>&quot;</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&quot;Based on commit: <span class="variable">$gitcommithash</span>&quot;</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>然后build中指定参数，以下两种写法都可以：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">    <span class="attr">buildno:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">gitcommithash:</span> <span class="string">cdc3b19</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">buildno=1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">gitcommithash=cdc3b19</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这时构建过程中使用的参数的值为args指定的值。在指定构建参数时也可以不指定值，在这种情况下，构建过程中使用的参数的值为运行Compose的环境中的值。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">args:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">buildno</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">gitcommithash</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用布尔值时的注意事项：YMAL中布尔类型的值（“true”、“false”、“yes”、“no”、“on”、“off”）必须用引号引起来，以便解析器将它们解释为字符串。</p><h5 id="4-cache-from"><a href="#4-cache-from" class="headerlink" title="(4)cache_from"></a>(4)cache_from</h5><p>在3.2版的配置文件格式中加入</p><p>指定缓存解析镜像列表。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">    <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">cache_from:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">alpine:latest</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">corp/web_app:3.14</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="5-labels"><a href="#5-labels" class="headerlink" title="(5)labels"></a>(5)labels</h5><p>在3.3版的配置文件格式中加入</p><p>将元数据以标签的形式添加到生成的镜像中。可以使用数组或字典两种格式。推荐使用反向DNS写法以避免和其他应用的标签冲突。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">com.example.description:</span> <span class="string">&quot;Accounting webapp&quot;</span></span><br><span class="line">    <span class="attr">com.example.department:</span> <span class="string">&quot;Finance&quot;</span></span><br><span class="line">    <span class="attr">com.example.label-with-empty-value:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;com.example.description=Accounting webapp&quot;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;com.example.department=Finance&quot;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;com.example.label-with-empty-value&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="6-network"><a href="#6-network" class="headerlink" title="(6)network"></a>(6)network</h5><p>在3.4版的配置文件格式中加入</p><p>设置容器网络连接以获取构建过程中的RUN指令。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">network:</span> <span class="string">custom_network_1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>设置为none可以在构建期间禁用网络连接。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">network:</span> <span class="string">none</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>(7)shm_size  </p><p>在3.5版的配置文件格式中加入</p><p>指定容器的&#x2F;dev&#x2F;shm分区大小。指定的值为表示字节数的整数值或表示字节值的字符串。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">shm_size:</span> <span class="number">10000000</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">shm_size:</span> <span class="string">&#x27;2gb&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="8-target"><a href="#8-target" class="headerlink" title="(8)target"></a>(8)target</h5><p>在3.4版的配置文件格式中加入</p><p>指定在Dockerfile中定义的构建阶段，即镜像只构建到指定阶段就停止构建。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">  <span class="attr">target:</span> <span class="string">prod</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>指定构建阶段为prod，即镜像只构建到prod阶段，prod阶段之后的内容不会被构建。</p><h4 id="2-cap-add、cap-drop"><a href="#2-cap-add、cap-drop" class="headerlink" title="2.cap_add、cap_drop"></a>2.cap_add、cap_drop</h4><p>添加或删除容器内核能力（capability）。完整的capability列表可查看man 7 capabilities。例如，让容器拥有所有内核能力：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cap_add:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">ALL</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>例如，删除NET_ADMIN和SYS_ADMIN能力：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cap_drop:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">NET_ADMIN</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">SYS_ADMIN</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用docker stack deploy时的注意事项：在swarm mode下部署堆栈时，cap_add和cap_drop配置项将被忽略。</p><h4 id="3-cgroup-parent"><a href="#3-cgroup-parent" class="headerlink" title="3.cgroup_parent"></a>3.cgroup_parent</h4><p>为容器指定一个可选的父控制组。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cgroup_parent:</span> <span class="string">m-executor-abcd</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用docker stack deploy时的注意事项：在swarm mode下部署堆栈时，cgroup_parent配置项将被忽略。</p><h4 id="4-command"><a href="#4-command" class="headerlink" title="4.command"></a>4.command</h4><p>覆盖容器启动后默认执行的命令。可以写成字符串形式。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">command:</span> <span class="string">bundle</span> <span class="string">exec</span> <span class="string">thin</span> <span class="string">-p</span> <span class="number">3000</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>也可以写成JSON数组形式。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">command:</span> [<span class="string">&quot;bundle&quot;</span>, <span class="string">&quot;exec&quot;</span>, <span class="string">&quot;thin&quot;</span>, <span class="string">&quot;-p&quot;</span>, <span class="string">&quot;3000&quot;</span>]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="5-configs"><a href="#5-configs" class="headerlink" title="5.configs"></a>5.configs</h4><p>在3.3版的配置文件格式中加入</p><p>为每个服务授予对配置（configs）的访问权限。支持short和long两种格式的语法。更多configs信息，参考configs。</p><p>注意：该配置（config）必须已存在或者在堆栈文件顶层configs配置项中定义，否则堆栈部署将失败。</p><p>short语法仅指定config名称来授予容器访问config的权限并将其挂载到容器的&#x2F;&lt;config_name&gt;上。source名称和目标挂载点都设置为config名称。例如以下示例，授予了redis服务对configs的my_config和my_other_config的访问权限，其中my_config的值设置到文件.&#x2F;my_config.txt的内容中，my_other_config定义为外部资源，这意味着它已经在Docker中通过运行docker config create命令或其他堆栈部署进行定义，如果外部config不存在，堆栈部署将会失败并显示config not found错误：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">my_config</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">my_other_config</span></span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_config:</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">./my_config.txt</span></span><br><span class="line">  <span class="attr">my_other_config:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>long语法提供了在服务的任务容器内如何创建config的更多粒度：</p><ul><li><p>source：Docker中存在的config名称。</p></li><li><p>target：指定要挂载到服务的任务容器的文件的路径加名称。如果未指定，默认为&#x2F;。</p></li><li><p>uid和gid：指定服务的任务容器所拥有的该文件的UID或GID。如果在LInux中未指定，两者都默认为0。不支持Windows。</p></li><li><p>mode：以八进制表示法指定要挂载到服务的任务容器的文件权限。例如，0444代表可读。默认值就为0444。config内容已挂载到临时文件系统中，所以不可写，如果设置了可写位将被忽略。可以设置可执行位。如果不熟悉UNIX文件权限模式，可以使用权限计算器 。<br>例如以下示例，指定config名称为my_config，授予redis服务对my_config的访问权限，指定要挂载到redis服务的任务容器的路径加文件名称为&#x2F;redis_config，指定UID和GID均为103，指定要挂载到服务的任务容器的文件权限为0440（group-readable），但该redis服务没有访问my_other_config的权限：</p></li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source:</span> <span class="string">my_config</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/redis_config</span></span><br><span class="line">        <span class="attr">uid:</span> <span class="string">&#x27;103&#x27;</span></span><br><span class="line">        <span class="attr">gid:</span> <span class="string">&#x27;103&#x27;</span></span><br><span class="line">        <span class="attr">mode:</span> <span class="number">0440</span></span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_config:</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">./my_config.txt</span></span><br><span class="line">  <span class="attr">my_other_config:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以授予服务访问多个config的权限，并且可以混合long和short语法。定义config并不意味着授予服务对其的访问权限。</p><h4 id="6-container-name"><a href="#6-container-name" class="headerlink" title="6.container_name"></a>6.container_name</h4><p>指定自定义容器的名称，而不是使用默认名称。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">container_name:</span> <span class="string">my-web-container</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>因为Docker容器的名称必须唯一，所以为一个服务指定了自定义容器名称后，该服务不能进行扩展。如果尝试为该服务扩容将会导致错误。</p><p>使用docker stack deploy时的注意事项：在swarm mode下部署堆栈时，container_name配置项将被忽略。</p><h4 id="7-credential-spec"><a href="#7-credential-spec" class="headerlink" title="7.credential_spec"></a>7.credential_spec</h4><p>在3.3版的配置文件格式中加入 在3.8或更高版本文件格式中支持将组托管服务帐户（GMSA）配置与Compose配置文件一起使用。</p><p>配置托管服务帐户的凭据规格（credential spec）。此选项仅用于使用Windows容器的服务。credential_spec配置必须采用file:&#x2F;&#x2F;或registry:&#x2F;&#x2F;格式。使用file:时，引用的文件必须存在于Docker数据目录的CredentialSpecs子目录中，在Windows上，Docker数据目录默认为C:\ProgramData\Docker\。以下示例从名为C:\ProgramData\Docker\CredentialSpecs\my-credential-spec.json的文件加载凭证规格：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">credential_spec:</span></span><br><span class="line">  <span class="attr">file:</span> <span class="string">my-credential-spec.json</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用registry:时，将从守护进程主机上的Windows注册表中读取凭据规格。其注册表值必须位于HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs。以下示例从注册表中名为my-credential-spec的值加载凭证规格：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">credential_spec:</span></span><br><span class="line">  <span class="attr">registry:</span> <span class="string">my-credential-spec</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>为服务配置GMSA凭据规格时，只需用config指定凭据规格。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">myservice:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">myimage:latest</span></span><br><span class="line">    <span class="attr">credential_spec:</span></span><br><span class="line">      <span class="attr">config:</span> <span class="string">my_credential_spec</span></span><br><span class="line"></span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_credentials_spec:</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">./my-credential-spec.json</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="8-depends-on"><a href="#8-depends-on" class="headerlink" title="8.depends_on"></a>8.depends_on</h4><p>指定服务之间的依赖关系，解决服务启动先后顺序问题。指定服务之间的依赖关系，将会导致以下行为：</p><ul><li><p>docker-compose up以依赖顺序启动服务。</p></li><li><p>docker-compose up SERVICE会自动包含SERVICE的依赖项。</p></li><li><p>docker-compose stop以依赖顺序停止服务。<br>例如以下示例：</p></li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">redis</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>启动时会先启动db和redis，最后才启动web。在使用docker-compose up web启动web时，也会启动db和redis，因为在web服务中指定了依赖关系。在停止时也在web之前先停止db和redis。</p><p>使用depends_on时的注意事项：</p><ol><li>服务不会等待该服务所依赖的服务完全启动之后才启动。例如上例，web不会等到db和redis完全启动之后才启动。</li><li>V3版不再支持的condition形式的depends_on。</li><li>V3版中，在swarm mode下部署堆栈时，depends_on配置项将被忽略。</li></ol><h4 id="9-deploy"><a href="#9-deploy" class="headerlink" title="9.deploy"></a>9.deploy</h4><p>在3版的配置文件格式中加入</p><p>指定部署和运行服务的相关配置。该配置仅在swarm mode下生效，并只能通过docker stack deploy命令部署，docker-compose up和docker-compose run命令将被忽略。例如：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:alpine</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">6</span></span><br><span class="line">      <span class="attr">placement:</span></span><br><span class="line">        <span class="attr">max_replicas_per_node:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="attr">parallelism:</span> <span class="number">2</span></span><br><span class="line">        <span class="attr">delay:</span> <span class="string">10s</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="attr">condition:</span> <span class="string">on-failure</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>deploy配置项中包含endpoint_mode、labels、mode、placement、replicas、resources、restart_policy、update_config等子配置项。</p><h5 id="1-endpoint-mode"><a href="#1-endpoint-mode" class="headerlink" title="(1)endpoint_mode"></a>(1)endpoint_mode</h5><p>在3.2版的配置文件格式中加入</p><p>为外部客户端连接到swarm指定服务发现方式：</p><ul><li><p>endpoint_mode: vip：Docker为服务分配了一个前端的虚拟IP，客户端通过该虚拟IP访问网络上的服务。Docker在客户端和服务的可用工作节点之间进行路由请求，而无须关系有多少节点正在参与该服务或这些节点的IP地址或者端口。这是默认设置。</p></li><li><p>endpoint_mode: dnsrr：DNS轮询（DNSRR），Docker设置服务的DNS条目，以便对服务名称的DNS查询返回IP地址列表，并且客户端通过轮询的方式直接连接到其中之一。<br>例如：</p></li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">wordpress:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:80&quot;</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">mode:</span> <span class="string">replicated</span></span><br><span class="line">      <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">endpoint_mode:</span> <span class="string">vip</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/d59fe82d/</id>
    <link href="https://luoyuanxiang.top/posts/d59fe82d/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文详细解析Docker Compose配置文件的结构与服务配置项，涵盖版本定义、服务部署、网络设置及数据卷配置，并通过具体示例展示各配置参数的用法与注意事项。</summary>
    <title>Docker Compose配置文件详解</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="docker" scheme="https://luoyuanxiang.top/tags/docker/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<p>docker运行容器</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">docker run -it -v G:/docker/mysql/data:/var/lib/mysql -v G:/docker/mysql/config/my.cnf:/etc/mysql/my.cnf --restart=always --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">docker run -it -v /var/lib/mysql:/var/lib/mysql -v /etc/mysql/my.cnf:/etc/mysql/my.cnf --restart=always --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">docker run -it -d -p 6379:6379 -v G:/docker/redis/config/redis.conf:/usr/local/etc/redis/redis.conf -v G:/docker/redis/data:/data --name docker-redis docker.io/redis redis-server /usr/local/etc/redis/redis.conf --appendonly yes</span><br><span class="line"></span><br><span class="line">命令解释</span><br><span class="line"></span><br><span class="line">-p 6379:6379 : 将容器的6379端口映射到主机的6379端口</span><br><span class="line"></span><br><span class="line">-v $PWD/docker/redis/conf:/etc/redis/redis.conf： 将主机中当前目录下的redis.conf映射成redis的启动配置文件</span><br><span class="line">-v $PWD /docker/redis/data:/data： 将主机中当前目录下 /docker/reids/data挂载到容器的/data</span><br><span class="line">redis-server /etc/redis/redis.conf：指定配置文件启动redis-server进程</span><br><span class="line"></span><br><span class="line">--requirepass &quot;123456&quot; :指定链接redis-server的密码</span><br><span class="line">--appendonly yes：开启数据持久化</span><br><span class="line">Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]</span><br><span class="line"></span><br><span class="line">-d, --detach=false         指定容器运行于前台还是后台，默认为false   </span><br><span class="line">-i, --interactive=false   打开STDIN，用于控制台交互  </span><br><span class="line">-t, --tty=false            分配tty设备，该可以支持终端登录，默认为false  </span><br><span class="line">-u, --user=&quot;&quot;              指定容器的用户  </span><br><span class="line">-a, --attach=[]            标准输入输出流和错误信息（必须是以非docker run -d启动的容器）</span><br><span class="line">-w, --workdir=&quot;&quot;           指定容器的工作目录</span><br><span class="line">-c, --cpu-shares=0        设置容器CPU权重，在CPU共享场景使用  </span><br><span class="line">-e, --env=[]               指定环境变量，容器中可以使用该环境变量  </span><br><span class="line">-m, --memory=&quot;&quot;            指定容器的内存上限  </span><br><span class="line">-P, --publish-all=false    指定容器暴露的端口  </span><br><span class="line">-p, --publish=[]           指定容器暴露的端口</span><br><span class="line">-h, --hostname=&quot;&quot;          指定容器的主机名  </span><br><span class="line">-v, --volume=[]            给容器挂载存储卷，挂载到容器的某个目录  </span><br><span class="line">--volumes-from=[]          给容器挂载其他容器上的卷，挂载到容器的某个目录</span><br><span class="line">--cap-add=[]               添加权限，权限清单详见：http://linux.die.net/man/7/capabilities  </span><br><span class="line">--cap-drop=[]              删除权限，权限清单详见：http://linux.die.net/man/7/capabilities  </span><br><span class="line">--cidfile=&quot;&quot;               运行容器后，在指定文件中写入容器PID值，一种典型的监控系统用法  </span><br><span class="line">--cpuset=&quot;&quot;                设置容器可以使用哪些CPU，此参数可以用来容器独占CPU  </span><br><span class="line">--device=[]                添加主机设备给容器，相当于设备直通  </span><br><span class="line">--dns=[]                   指定容器的dns服务器  </span><br><span class="line">--dns-search=[]            指定容器的dns搜索域名，写入到容器的/etc/resolv.conf文件  </span><br><span class="line">--entrypoint=&quot;&quot;            覆盖image的入口点  </span><br><span class="line">--env-file=[]              指定环境变量文件，文件格式为每行一个环境变量  </span><br><span class="line">--expose=[]                指定容器暴露的端口，即修改镜像的暴露端口  </span><br><span class="line">--link=[]                  指定容器间的关联，使用其他容器的IP、env等信息  </span><br><span class="line">--lxc-conf=[]              指定容器的配置文件，只有在指定--exec-driver=lxc时使用  </span><br><span class="line">--name=&quot;&quot;                  指定容器名字，后续可以通过名字进行容器管理，links特性需要使用名字  </span><br><span class="line">--net=&quot;bridge&quot;             容器网络设置:</span><br><span class="line">bridge 使用docker daemon指定的网桥     </span><br><span class="line">host     //容器使用主机的网络  </span><br><span class="line">container:NAME_or_ID  &gt;//使用其他容器的网路，共享IP和PORT等网络资源  </span><br><span class="line">none 容器使用自己的网络（类似--net=bridge），但是不进行配置</span><br><span class="line">--privileged=false         指定容器是否为特权容器，特权容器拥有所有的capabilities  </span><br><span class="line">--restart=&quot;no&quot;             指定容器停止后的重启策略:</span><br><span class="line">no：容器退出时不重启  </span><br><span class="line">on-failure：容器故障退出（返回值非零）时重启</span><br><span class="line">always：容器退出时总是重启  </span><br><span class="line">--rm=false                 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)  </span><br><span class="line">--sig-proxy=true           设置由代理接受并处理信号，但是SIGCHLD、SIGSTOP和SIGKILL不能被代理</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>docker补充命令</p><ul><li><p><code>docker start 容器名（容器ID也可以）</code>: 启动容器</p></li><li><p><code>docker stop 容器名（容器ID也可以）</code>: 停止容器</p></li><li><p><code>docker run 命令加 -d 参数</code>，docker 会将容器放到后台运行</p></li><li><p><code>docker ps</code> 正在运行的容器</p></li><li><p><code>docker logs --tail 10 -tf 容器名</code> 查看容器的日志文件,加-t是加上时间戳，f是跟踪某个容器的最新日志而不必读整个日志文件</p></li><li><p><code>docker top</code> 容器名 查看容器内部运行的进程</p></li><li><p><code>docker exec -d 容器名 touch /etc/new_config_file</code> 通过后台命令创建一个空文件</p></li><li><p><code>docker run --restart=always --name 容器名 -d ubuntu /bin/sh -c &quot;while true;do echo hello world; sleep 1; done&quot;</code> 无论退出代码是什么，docker都会自动重启容器，可以设置 –restart&#x3D;on-failure:5 自动重启的次数</p></li><li><p><code>docker inspect</code> 容器名 对容器进行详细的检查，可以加 –format&#x3D;‘{(.State.Running)}’ 来获取指定的信息</p></li><li><p><code>docker rm</code> 容器ID 删除容器，注，运行中的容器无法删除</p></li><li><p><code>docker rm $(docker ps -aq)</code> 删除所有容器</p></li><li><p><code>docker rmi $(docker images -aq)</code> 删除所有镜像</p></li><li><p><code>docker images</code> 列出镜像</p></li><li><p><code>docker pull</code> 镜像名:标签 拉镜像</p></li><li><p><code>docker search</code> 查找docker Hub 上公共的可用镜像</p></li><li><p><code>docker build -t=&#39;AT/web_server:v1&#39;</code> 命令后面可以直接加上github仓库的要目录下存在的Dockerfile文件。 命令是编写Dockerfile 之后使用的。-t选项为新镜像设置了仓库和名称:标签</p></li><li><p><code>docker login</code> 登陆到Docker Hub，个人认证信息将会保存到$HOME&#x2F;.dockercfg,</p></li><li><p><code>docker commit -m=&quot;comment &quot; --author=&quot;AT&quot;</code> 容器ID 镜像的用户名&#x2F;仓库名:标签 不推荐这种方法，推荐dockerfile</p></li><li><p><code>docker history</code> 镜像ID 深入探求镜像是如何构建出来的</p></li><li><p><code>docker port</code> 镜像ID 端口 查看映射情况的容器的ID和容器的端口号，假设查询80端口对应的映射的端口</p></li><li><p><code>run</code> 运行一个容器， -p 8080:80 将容器内的80端口映射到docker宿主机的某一特定端口，将容器的80端口绑定到宿主机的8080端口，另 127.0.0.1:80:80 是将容器的80端口绑定到宿主机这个IP的80端口上，-P 是将容器内的80端口对本地的宿主机公开</p></li><li><p><code>docker push</code> 镜像名 将镜像推送到 Docker Hub</p></li><li><p><code>docker rmi</code> 镜像名 删除镜像</p></li><li><p><code>docker attach</code> 容器ID 进入容器</p></li><li><p><code>docker network create --subnet=172.171.0.0/16</code> docker-at 选取172.172.0.0网段</p></li><li><p><code>docker build</code> 就可以加 -ip指定容器ip 172.171.0.10 了</p></li></ul><p>docker 镜像操作</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">docker images：列出images</span><br><span class="line">   </span><br><span class="line">docker images -a：列出所有的images（包含历史）</span><br><span class="line">   </span><br><span class="line">docker images --tree ：显示镜像的所有层(layer)</span><br><span class="line">   </span><br><span class="line">docker rmi  &lt;image ID&gt;：删除一个或多个image</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>docker 容器操作</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">启动容器</span></span><br><span class="line">docker start &lt;ContainerId(或者name)&gt;</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">停止容器</span></span><br><span class="line">docker stop &lt;ContainerId(或者name)&gt;</span><br><span class="line">docker stop -t=60 容器ID或容器名</span><br><span class="line">参数 -t：关闭容器的限时，如果超时未能关闭则用kill强制关闭，默认值10s，这个时间用于容器的自己保存状态</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">重启容器</span></span><br><span class="line">docker restart &lt;ContainerId(或者name)&gt;</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">删除容器</span></span><br><span class="line">docker rm &lt;ContainerId(或者name)&gt;</span><br><span class="line">docker rm -f 容器ID或容器名 ：直接删除正在运行的容器</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">删除所有容器</span></span><br><span class="line">docker rm $(docker ps -a -q)</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">进入容器</span></span><br><span class="line">docker exec -it containerID /bin/bash</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看容器日志</span></span><br><span class="line">docker logs -f -t --tail 行数 容器名</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Dockerfile语法</p><ul><li><p><code>MAINTAINER</code> 标识镜像的作者和联系方式</p></li><li><p><code>EXPOSE</code> 可以指定多个EXPOSE向外部公开多个端口，可以帮助多个容器链接</p></li><li><p><code>FROM</code> 指令指定一个已经存在的镜像</p></li><li><p><code>RUN</code> 运行命令,会在shell 里使用命令包装器 &#x2F;bin&#x2F;sh -c 来执行。如果是在一个不支持shell 的平台上运行或者不希望在shell 中运行，也可以 使用exec 格式 的RUN指令</p></li><li><p><code>ENV REFRESHED_AT</code> 环境变量 这个环境亦是用来表明镜像模板最后的更新时间</p></li><li><p><code>VOLUME</code> 容器添加卷。一个卷是可以 存在于一个或多个容器内的特定的目录，对卷的修改是立刻生效的，对卷的修改不会对更新镜像产品影响，例:VOLUME[“&#x2F;opt&#x2F;project”,“&#x2F;data”]</p></li><li><p><code>ADD</code> 将构建环境 下的文件 和目录复制到镜像 中。例 ADD nginx.conf &#x2F;conf&#x2F;nginx.conf 也可以是取url 的地址文件，如果是压缩包，ADD命令会自动解压、</p></li><li><p><code>USER</code> 指定镜像用那个USER 去运行</p></li><li><p><code>COPY</code> 是复制本地文件，而不会去做文件提取（解压包不会自动解压） 例：COPY conf.d&#x2F; &#x2F;etc&#x2F;apache2&#x2F; 将本地conf.d目录中的文件复制到&#x2F;etc&#x2F;apache2&#x2F;目录中</p></li><li><p><code>CMD</code> 类似于RUN指令，CMD指令也可用于运行任何命令或应用程序，不过，二者的运行时间点不同</p><ul><li><p><code>RUN</code> 指令运行于映像文件构建过程中，而CMD指令运行于基于Dockerfile构建出的新镜像文件启动一个容器时。</p></li><li><p><code>CMD</code>指令的首要目的在于为启动的容器指定默认要运行的程序，且其运行结束后，容器也将终止;不过，CMD指定的命令其可以被docker run的命令行选项所覆盖</p></li><li><p>在Dockerfile中可以存在多个CMD指令，但仅最后一个生效</p></li></ul></li></ul><blockquote><p>你要尽全力保护你的梦想。那些嘲笑你梦想的人，因为他们必定会失败，他们想把你变成和他们一样的人。 —《当幸福来敲门》</p></blockquote>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/e9537808/</id>
    <link href="https://luoyuanxiang.top/posts/e9537808/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文详细介绍了Docker容器的运行命令、参数解析及常用操作，包括MySQL和Redis的容器化部署示例，并涵盖了Docker镜像管理、容器生命周期操作及Dockerfile语法指南。</summary>
    <title>Docker容器操作与命令详解</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <category term="优化" scheme="https://luoyuanxiang.top/tags/%E4%BC%98%E5%8C%96/"/>
    <category term="性能" scheme="https://luoyuanxiang.top/tags/%E6%80%A7%E8%83%BD/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a><strong>前言</strong></h1><p>代码优化，一个很重要的课题。可能有些人觉得没用，一些细小的地方有什么好修改的，改与不改对于代码的运行效率有什么影响呢？这个问题我是这么考虑的，就像大海里面的鲸鱼一样，它吃一条小虾米有用吗？没用，但是，吃的小虾米一多之后，鲸鱼就被喂饱了。代码优化也是一样，如果项目着眼于尽快无 BUG 上线，那么此时可以抓大放小，代码的细节可以不精打细磨；但是如果有足够的时间开发、维护代码，这时候就必须考虑每个可以优化的细节了，一个一个细小的优化点累积起来，对于代码的运行效率绝对是有提升的。</p><p>代码优化的目标是：</p><p>1、减小代码的体积</p><p>2、提高代码运行的效率</p><h1 id="代码优化细节"><a href="#代码优化细节" class="headerlink" title="代码优化细节"></a><strong>代码优化细节</strong></h1><p>1、尽量指定类、方法的 <code>final</code> 修饰符</p><p>带有 <code>final</code> 修饰符的类是不可派生的。在 <code>Java</code> 核心 <code>API</code> 中，有许多应用 <code>final</code> 的例子，例如 <code>java.lang.String</code> ，整个类都是 <code>final</code> 的。为类指定 <code>final</code> 修饰符可以让类不可以被继承，为方法指定 <code>final</code> 修饰符可以让方法不可以被重写。如果指定了一个类为 <code>final</code> ，则该类所有的方法都是 <code>final</code> 的。 <code>Java</code> 编译器会寻找机会内联所有的 <code>final</code> 方法，内联对于提升 <code>Java</code> 运行效率作用重大，具体参见 <code>Java</code> 运行期优化。此举能够使性能平均提高 50%。</p><p>2、尽量重用对象</p><p>特别是 <code>String</code> 对象的使用，出现字符串连接时应该使用 <code>StringBuilder/StringBuffer</code> 代替。由于 <code>Java</code> 虚拟机不仅要花时间生成对象，以后可能还需要花时间对这些对象进行垃圾回收和处理，因此，生成过多的对象将会给程序的性能带来很大的影响。</p><p>3、尽可能使用局部变量</p><p>调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快，其他变量，如静态变量、实例变量等，都在堆中创建，速度较慢。另外，栈中创建的变量，随着方法的运行结束，这些内容就没了，不需要额外的垃圾回收。</p><p>4、及时关闭流</p><p><code>Java</code> 编程过程中，进行数据库连接、I&#x2F;O 流操作时务必小心，在使用完毕后，及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销，稍有不慎，将会导致严重的后果。</p><p>5、尽量减少对变量的重复计算</p><p>明确一个概念，对方法的调用，即使方法中只有一句语句，也是有消耗的，包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; list.size(); i++) &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>建议替换为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>, <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> list.size(); i &lt; length; i++) &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，在 <code>list.size()</code> 很大的时候，就减少了很多的消耗</p><p>6、尽量采用懒加载的策略，即在需要的时候才创建</p><p>例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;aaa&quot;</span>;</span><br><span class="line"><span class="keyword">if</span> (i == <span class="number">1</span>) &#123;</span><br><span class="line">    list.add(str);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>建议替换为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (i == <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;aaa&quot;</span>;</span><br><span class="line">    list.add(str);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>7、慎用异常</p><p>异常对性能不利。抛出异常首先要创建一个新的对象， <code>Throwable</code> 接口的构造函数调用名为 <code>fillInStackTrace()</code> 的本地同步方法， <code>fillInStackTrace()</code> 方法检查堆栈，收集调用跟踪信息。只要有异常被抛出，<code>Java</code> 虚拟机就必须调整调用堆栈，因为在处理过程中创建了一个新的对象。异常只能用于错误处理，不应该用来控制程序流程。</p><p>8、不要在循环中使用 <code>try…catch…</code> ，应该把其放在最外层</p><p>除非不得已。如果毫无理由地这么写了，只要你的领导资深一点、有强迫症一点，八成就要骂你为什么写出这种垃圾代码来了</p><p>9、如果能估计到待添加的内容长度，为底层以数组方式实现的集合、工具类指定初始长度</p><p>比如 <code>ArrayList</code>、<code>LinkedLlist</code>、<code>StringBuilder</code>、<code>StringBuffer</code>、<code>HashMap</code>、<code>HashSet</code> 等等，以 <code>StringBuilder</code> 为例：</p><p>（1） <code>StringBuilder()</code> 　　　　　　&#x2F;&#x2F; 默认分配 16 个字符的空间</p><p>（2） <code>StringBuilder(int size)</code> 　　&#x2F;&#x2F; 默认分配 size 个字符的空间</p><p>（3） <code>StringBuilder(String str)</code> 　&#x2F;&#x2F; 默认分配 16 个字符 + <code>str.length()</code> 个字符空间</p><p>可以通过类（这里指的不仅仅是上面的 <code>StringBuilder</code> ）的来设定它的初始化容量，这样可以明显地提升性能。比如 <code>StringBuilder</code> 吧， <code>length</code> 表示当前的 <code>StringBuilder</code> 能保持的字符数量。因为当 <code>StringBuilder</code> 达到最大容量的时候，它会将自身容量增加到当前的 2 倍再加 2，无论何时只要 StringBuilder 达到它的最大容量，它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中 —- 这是十分耗费性能的一个操作。试想，如果能预估到字符数组中大概要存放 5000 个字符而不指定长度，最接近 5000 的 2 次幂是 4096，每次扩容加的 2 不管，那么：</p><p>（1）在 4096 的基础上，再申请 8194 个大小的字符数组，加起来相当于一次申请了 12290 个大小的字符数组，如果一开始能指定 5000 个大小的字符数组，就节省了一倍以上的空间</p><p>（2）把原来的 4096 个字符拷贝到新的的字符数组中去</p><p>这样，既浪费内存空间又降低代码运行效率。所以，给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的，这会带来立竿见影的效果。但是，注意，像 HashMap 这种是以数组 + 链表实现的集合，别把初始大小和你估计的大小设置得一样，因为一个 table 上只连接一个对象的可能性几乎为 0。初始大小建议设置为 2 的 N 次幂，如果能估计到有 2000 个元素，设置成 new HashMap(128)、new HashMap(256) 都可以。</p><p>10、当复制大量数据时，使用 <code>System.arraycopy()</code> 命令</p><p>11、乘法和除法使用移位操作</p><p>例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (val = <span class="number">0</span>; val &lt; <span class="number">100000</span>; val += <span class="number">5</span>) &#123;</span><br><span class="line">    a = val * <span class="number">8</span>;</span><br><span class="line">    b = val / <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用移位操作可以极大地提高性能，因为在计算机底层，对位的操作是最方便、最快的，因此建议修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (val = <span class="number">0</span>; val &lt; <span class="number">100000</span>; val += <span class="number">5</span>) &#123;</span><br><span class="line">    a = val &lt;&lt; <span class="number">3</span>;</span><br><span class="line">    b = val &gt;&gt; <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>移位操作虽然快，但是可能会使代码不太好理解，因此最好加上相应的注释。</p><p>12、循环内不要不断创建对象引用</p><p>例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= count; i++) &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种做法会导致内存中有 <code>count</code> 份 <code>Object</code> 对象引用存在，<code>count</code> 很大的话，就耗费内存了，建议为改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= count; i++) &#123; </span><br><span class="line">    obj = <span class="keyword">new</span> <span class="title class_">Object</span>(); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>13、基于效率和类型检查的考虑，应该尽可能使用 <code>array</code> ，无法确定数组大小时才使用 <code>ArrayList</code></p><p>14、尽量使用 <code>HashMap</code>、<code>ArrayList</code>、<code>StringBuilder</code> ，除非线程安全需要，否则不推荐使用 <code>Hashtable</code>、<code>Vector</code>、<code>StringBuffer</code> ，后三者由于使用同步机制而导致了性能开销</p><p>15、不要将数组声明为 <code>public static final</code></p><p>因为这毫无意义，这样只是定义了引用为 <code>static final</code> ，数组的内容还是可以随意改变的，将数组声明为 <code>public</code> 更是一个安全漏洞，这意味着这个数组可以被外部类所改变</p><p>16、尽量在合适的场合使用单例</p><p>使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率，但并不是所有地方都适用于单例，简单来说，单例主要适用于以下三个方面：</p><p>（1）控制资源的使用，通过线程同步来控制资源的并发访问</p><p>（2）控制实例的产生，以达到节约资源的目的</p><p>（3）控制数据的共享，在不建立直接关联的条件下，让多个不相关的进程或线程之间实现通信</p><p>17、尽量避免随意使用静态变量</p><p>要知道，当某个对象被定义为 <code>static</code> 的变量所引用，那么 gc 通常是不会回收这个对象所占有的堆内存的，如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">B</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">B</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时静态变量 b 的生命周期与 A 类相同，如果 A 类不被卸载，那么引用 B 指向的 B 对象会常驻内存，直到程序终止</p><p>18、及时清除不再需要的会话</p><p>为了清除不再活动的会话，许多应用服务器都有默认的会话超时时间，一般为 30 分钟。当应用服务器需要保存更多的会话时，如果内存不足，那么操作系统会把部分数据转移到磁盘，应用服务器也可能根据 MRU（最近最频繁使用）算法把部分不活跃的会话转储到磁盘，甚至可能抛出内存不足的异常。如果会话要被转储到磁盘，那么必须要先被序列化，在大规模集群中，对对象进行序列化的代价是很昂贵的。因此，当会话不再需要时，应当及时调用 HttpSession 的 invalidate() 方法清除会话。</p><p>19、实现 <code>RandomAccess</code> 接口的集合比如 <code>ArrayList</code> ，应当使用最普通的 <code>for</code> 循环而不是 <code>foreach</code> 循环来遍历</p><p>这是 <code>JDK</code> 推荐给用户的。<code>JDK API</code> 对于 <code>RandomAccess</code> 接口的解释是：实现 <code>RandomAccess</code> 接口用来表明其支持快速随机访问，此接口的主要目的是允许一般的算法更改其行为，从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明，实现 <code>RandomAccess</code> 接口的类实例，假如是随机访问的，使用普通 <code>for</code> 循环效率将高于使用 <code>foreach</code> 循环；反过来，如果是顺序访问的，则使用 <code>Iterator</code> 会效率更高。可以使用类似如下的代码作判断：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> RandomAccess) &#123; </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; list.size(); i++)&#123;&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    Iterator&lt;?&gt; iterator = list.iterable(); </span><br><span class="line">    <span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">        iterator.next()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>foreach</code> 循环的底层实现原理就是迭代器 <code>Iterator</code> ，参见 <code>Java</code> 语法糖 1：可变长度参数以及 <code>foreach</code> 循环原理。所以后半句” 反过来，如果是顺序访问的，则使用 <code>Iterator</code> 会效率更高” 的意思就是顺序访问的那些类实例，使用 <code>foreach</code> 循环去遍历。</p><p>20、使用同步代码块替代同步方法</p><p>这点在多线程模块中的 <code>synchronized</code> 锁方法块一文中已经讲得很清楚了，除非能确定一整个方法都是需要进行同步的，否则尽量使用同步代码块，避免对那些不需要进行同步的代码也进行了同步，影响了代码执行效率。</p><p>21、将常量声明为 <code>static final</code> ，并以大写命名</p><p>这样在编译期间就可以把这些内容放入常量池中，避免运行期间计算生成常量的值。另外，将常量的名字以大写命名也可以方便区分出常量与变量</p><p>22、不要创建一些不使用的对象，不要导入一些不使用的类</p><p>这毫无意义，如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”，那么请删除这些无用的内容</p><p>23、程序运行过程中避免使用反射</p><p>关于，请参见反射。反射是 <code>Java</code> 提供给用户一个很强大的功能，功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制，特别是 <code>Method</code> 的 <code>invoke</code> 方法，如果确实有必要，一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存 —- 用户只关心和对端交互的时候获取最快的响应速度，并不关心对端的项目启动花多久时间。</p><p>24、使用数据库连接池和线程池</p><p>这两个池都是用于重用对象的，前者可以避免频繁地打开和关闭连接，后者可以避免频繁地创建和销毁线程</p><p>25、使用带缓冲的输入输出流进行 IO 操作</p><p>带缓冲的输入输出流，即 <code>BufferedReader</code>、<code>BufferedWriter</code>、<code>BufferedInputStream</code>、<code>BufferedOutputStream</code> ，这可以极大地提升 IO 效率</p><p>26、顺序插入和随机访问比较多的场景使用 <code>ArrayList</code> ，元素删除和中间插入比较多的场景使用 <code>LinkedList</code></p><p>参考：<a href="https://luoyuanxiang.top/go.html?u=aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vd3d3Y25ibG9nc2NvbS9wLzgwMzY0MTEuaHRtbA"><strong>https://www.cnblogs.com/wwwcnblogscom/p/8036411.html</strong></a></p><p>27、不要让 <code>public</code> 方法中有太多的形参</p><p><code>public</code> 方法即对外提供的方法，如果给这些方法太多形参的话主要有两点坏处：</p><p>1、违反了面向对象的编程思想，<code>Java</code> 讲求一切都是对象，太多的形参，和面向对象的编程思想并不契合</p><p>2、参数太多势必导致方法调用的出错概率增加</p><p>至于这个” 太多” 指的是多少个，3、4 个吧。比如我们用 JDBC 写一个 <code>insertStudentInfo</code> 方法，有 10 个学生信息字段要插如 <code>Student</code> 表中，可以把这 10 个参数封装在一个实体类中，作为 <code>insert</code> 方法的形参</p><p>28、字符串变量和字符串常量 <code>equals</code> 的时候将字符串常量写在前面</p><p>这是一个比较常见的小技巧了，如果有以下代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;123&quot;</span>;</span><br><span class="line"><span class="keyword">if</span> (str.equals(<span class="string">&quot;123&quot;</span>)) &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>建议修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;123&quot;</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="string">&quot;123&quot;</span>.equals(str))</span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这么做主要是可以避免空指针异常</p><p>29、请知道，在 <code>java</code> 中 <code>if (i == 1)</code> 和 <code>if (1 == i)</code> 是没有区别的，但从阅读习惯上讲，建议使用前者</p><p>平时有人问，<code>if (i == 1)</code> 和 <code>if (1== i)</code> 有没有区别，这就要从 <code>C/C++</code> 讲起。</p><p>在 <code>C/C++</code> 中，<code>if (i == 1)</code> 判断条件成立，是以 0 与非 0 为基准的，0 表示 <code>false</code>，非 0 表示 <code>true</code>，如果有这么一段代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">if</span> (i == <span class="number">1</span>) &#123;</span><br><span class="line">...</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>C/C++</code> 判断 <code>i==1</code> 不成立，所以以 0 表示，即 <code>false</code>。但是如果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">if</span> (i = <span class="number">1</span>) &#123;</span><br><span class="line">    ... </span><br><span class="line">&#125; <span class="keyword">else</span> &#123; </span><br><span class="line">    ... </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>万一程序员一个不小心，把 <code>if (i == 1)</code> 写成<code>if (i = 1)</code>，这样就有问题了。在 <code>if</code> 之内将 <code>i</code> 赋值为 1，<code>if</code> 判断里面的内容非 0，返回的就是 <code>true</code> 了，但是明明 i 为 2，比较的值是 1，应该返回的 <code>false</code>。这种情况在 <code>C/C++</code> 的开发中是很可能发生的并且会导致一些难以理解的错误产生，所以，为了避免开发者在 <code>if</code> 语句中不正确的赋值操作，建议将 <code>if</code> 语句写为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="number">1</span> == i) &#123; </span><br><span class="line">    ...</span><br><span class="line">&#125; <span class="keyword">else</span> &#123; </span><br><span class="line">    ... </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，即使开发者不小心写成了<code>1 = i</code>，<code>C/C++</code> 编译器也可以第一时间检查出来，因为我们可以对一个变量赋值 <code>i</code> 为 1，但是不能对一个常量赋值 1 为 <code>i</code>。</p><p>但是，在 <code>Java</code> 中，<code>C/C++</code> 这种<code>if (i = 1)</code> 的语法是不可能出现的，因为一旦写了这种语法，<code>Java</code> 就会编译报错<code>Type mismatch: cannot convert from int to boolean</code>。但是，尽管 <code>Java</code> 的<code>if (i == 1)</code> 和<code>if (1 == i)</code> 在语义上没有任何区别，但是从阅读习惯上讲，建议使用前者会更好些。</p><p>30、不要对数组使用 <code>toString()</code> 方法</p><p>看一下对数组使用 <code>toString()</code> 打印出来的是什么：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123; </span><br><span class="line">    <span class="type">int</span>[] is = <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;;</span><br><span class="line">    System.out.println(is.toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果是：<br><code>[I@18a992f</code><br>本意是想打印出数组内容，却有可能因为数组引用 <code>is</code> 为空而导致空指针异常。不过虽然对数组 <code>toString()</code> 没有意义，但是对集合 <code>toString()</code> 是可以打印出集合里面的内容的，因为集合的父类 <code>AbstractCollections&lt;E&gt;</code> 重写了 <code>Object</code> 的 <code>toString()</code> 方法。</p><p>31、不要对超出范围的基本数据类型做向下强制转型</p><p>这绝不会得到想要的结果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">l</span> <span class="operator">=</span> <span class="number">12345678901234L</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> (<span class="type">int</span>)l;</span><br><span class="line">    System.out.println(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可能期望得到其中的某几位，但是结果却是：</p><p>1942892530</p><p>解释一下。<code>Java</code> 中 <code>long</code> 是 8 个字节 64 位的，所以 12345678901234 在计算机中的表示应该是：</p><p>0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010</p><p>一个 <code>int</code> 型数据是 4 个字节 32 位的，从低位取出上面这串二进制数据的前 32 位是：</p><p>0111 0011 1100 1110 0010 1111 1111 0010</p><p>这串二进制表示为十进制 1942892530，所以就是我们上面的控制台上输出的内容。从这个例子上还能顺便得到两个结论：</p><p>1、整型默认的数据类型是 <code>int</code>，<code>long l = 12345678901234L</code> ，这个数字已经超出了 <code>int</code> 的范围了，所以最后有一个 L，表示这是一个 <code>long</code> 型数。顺便，浮点型的默认类型是 <code>double</code> ，所以定义 <code>float</code> 的时候要写成 <code>float f = 3.5f</code></p><p>2、接下来再写一句 <code>int ii = l + i;</code> 会报错，因为 <code>long + int</code> 是一个 <code>long</code> ，不能赋值给 <code>int</code></p><p>32、公用的集合类中不使用的数据一定要及时 <code>remove</code> 掉</p><p>如果一个集合类是公用的（也就是说不是方法里面的属性），那么这个集合里面的元素是不会自动释放的，因为始终有引用指向它们。所以，如果公用集合里面的某些数据不使用而不去 remove 掉它们，那么将会造成这个公用集合不断增大，使得系统有内存泄露的隐患。</p><p>33、把一个基本数据类型转为字符串，<code>基本数据类型.toString ()</code> 是最快的方式、<code>String.valueOf(数据)</code> 次之、数据 +”” 最慢</p><p>把一个基本数据类型转为一般有三种方式，我有一个 <code>Integer</code> 型数据 <code>i</code>，可以使用 <code>i.toString()</code>、<code>String.valueOf(i)</code>、<code>i+&quot;&quot;</code> 三种方式，三种方式的效率如何，看一个测试：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">loopTime</span> <span class="operator">=</span> <span class="number">50000</span>;</span><br><span class="line">    <span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; </span><br><span class="line">    <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis(); </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; loopTime; j++) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> String.valueOf(i);</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">&quot;String.valueOf()：&quot;</span> + (System.currentTimeMillis() - startTime) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">    startTime = System.currentTimeMillis(); </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; loopTime; j++) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> i.toString();</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">&quot;Integer.toString()：&quot;</span> + (System.currentTimeMillis() - startTime) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">    startTime = System.currentTimeMillis(); </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; loopTime; j++) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> i + <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">System.out.println(<span class="string">&quot;i + \&quot;\&quot;：&quot;</span> + (System.currentTimeMillis() - startTime) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行结果为：<br><code>String.valueOf()：11ms Integer.toString()：5ms i + &quot;&quot;：25ms</code><br>所以以后遇到把一个基本数据类型转为 <code>String</code> 的时候，优先考虑使用 <code>toString()</code> 方法。至于为什么，很简单：</p><p>1、 <code>String.valueOf()</code> 方法底层调用了 <code>Integer.toString()</code> 方法，但是会在调用前做空判断</p><p>2、 <code>Integer.toString()</code> 方法就不说了，直接调用了</p><p>3、<code>i + “”</code> 底层使用了 <code>StringBuilder</code> 实现，先用 <code>append</code> 方法拼接，再用 <code>toString()</code> 方法获取字符串</p><p>三者对比下来，明显是 2 最快、1 次之、3 最慢</p><p>34、使用最有效率的方式去遍历 <code>Map</code></p><p>遍历 <code>Map</code> 的方式有很多，通常场景下我们需要的是遍历 <code>Map</code> 中的 <code>Key</code> 和 <code>Value</code>，那么推荐使用的、效率最高的方式是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    HashMap&lt;String, String&gt; hm = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line">    hm.put(<span class="string">&quot;111&quot;</span>, <span class="string">&quot;222&quot;</span>);</span><br><span class="line">    Set&lt;Map.Entry&lt;String, String&gt;&gt; entrySet = hm.entrySet();</span><br><span class="line">    Iterator&lt;Map.Entry&lt;String, String&gt;&gt; iter = entrySet.iterator();</span><br><span class="line">    <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">        Map.Entry&lt;String, String&gt; entry = iter.next();</span><br><span class="line">        System.out.println(entry.getKey() + <span class="string">&quot;\t&quot;</span> + entry.getValue());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你只是想遍历一下这个 <code>Map</code> 的 <code>key</code> 值，那用 <code>Set&lt;String&gt; keySet = hm.keySet();</code> 会比较合适一些</p><p>35、对资源的 <code>close()</code> 建议分开操作</p><p>意思是，比如我有这么一段代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    XXX.close();</span><br><span class="line">    YYY.close();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>建议修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123; </span><br><span class="line">    XXX.close(); </span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123; </span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">try</span> &#123; </span><br><span class="line">    YYY.close(); </span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123; </span><br><span class="line">    ... </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然有些麻烦，却能避免资源泄露。我们想，如果没有修改过的代码，万一 <code>XXX.close()</code> 抛异常了，那么就进入了 <code>cath</code> 块中了， <code>YYY.close()</code> 不会执行，<code>YYY</code> 这块资源就不会回收了，一直占用着，这样的代码一多，是可能引起资源句柄泄露的。而改为下面的写法之后，就保证了无论如何 <code>XXX</code> 和 <code>YYY</code> 都会被 <code>close</code> 掉。</p><blockquote><p>只有把抱怨环境的心情，化为上进的力量，才是成功的保证。—— 罗曼・罗兰</p></blockquote>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/856b597f/</id>
    <link href="https://luoyuanxiang.top/posts/856b597f/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文详细介绍了33条Java代码优化的实用技巧，涵盖性能提升、内存管理、编码规范等方面，通过具体示例说明如何减少代码体积、提高运行效率，帮助开发者编写更高质量的代码。</summary>
    <title>Java代码优化33条实用技巧</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="生活随笔" scheme="https://luoyuanxiang.top/categories/%E7%94%9F%E6%B4%BB%E9%9A%8F%E7%AC%94/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <content>
      <![CDATA[<h2 id="基础篇"><a href="#基础篇" class="headerlink" title="基础篇"></a>基础篇</h2><h3 id="1、-Java语言有哪些特点"><a href="#1、-Java语言有哪些特点" class="headerlink" title="1、 Java语言有哪些特点"></a>1、 Java语言有哪些特点</h3><ol><li>简单易学、有丰富的类库<br>阿里内部资料</li><li>面向对象（Java最重要的特性，让程序耦合度更低，内聚性更高）</li><li>与平台无关性（JVM是Java跨平台使用的根本）</li><li>可靠安全</li><li>支持多线程</li></ol><h3 id="2、面向对象和面向过程的区别"><a href="#2、面向对象和面向过程的区别" class="headerlink" title="2、面向对象和面向过程的区别"></a>2、面向对象和面向过程的区别</h3><p>面向过程：是分析解决问题的步骤，然后用函数把这些步骤一步一步地实现，然后在使用的时候一<br>一调用则可。性能较高，所以单片机、嵌入式开发等一般采用面向过程开发<br>面向对象：是把构成问题的事务分解成各个对象，而建立对象的目的也不是为了完成一个个步骤，<br>而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特<br>性，所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说，比面向过程要<br>低。</p><h3 id="3-、八种基本数据类型的大小，以及他们的封装类"><a href="#3-、八种基本数据类型的大小，以及他们的封装类" class="headerlink" title="3 、八种基本数据类型的大小，以及他们的封装类"></a>3 、八种基本数据类型的大小，以及他们的封装类</h3><table><thead><tr><th align="center">基本类型</th><th align="center">大小（字节）</th><th align="center">默认值</th><th align="center">封装类</th></tr></thead><tbody><tr><td align="center">byte</td><td align="center">1</td><td align="center">(byte)0</td><td align="center">Byte</td></tr><tr><td align="center">short</td><td align="center">2</td><td align="center">(short)0</td><td align="center">Short</td></tr><tr><td align="center">int</td><td align="center">4</td><td align="center">0</td><td align="center">Integer</td></tr><tr><td align="center">long</td><td align="center">8</td><td align="center">0L</td><td align="center">Long</td></tr><tr><td align="center">float</td><td align="center">4</td><td align="center">0.0f</td><td align="center">Float</td></tr><tr><td align="center">double</td><td align="center">8</td><td align="center">0.0d</td><td align="center">Double</td></tr><tr><td align="center">boolean</td><td align="center">-</td><td align="center">false</td><td align="center">Boolean</td></tr><tr><td align="center">char</td><td align="center">1</td><td align="center">\u0000(null)</td><td align="center">Character</td></tr></tbody></table><p><strong>注</strong></p><ol><li><p>int是基本数据类型，Integer是int的封装类，是引用类型。int默认值是0，而Integer默认值<br>是null，所以Integer能区分出0和null的情况。一旦java看到null，就知道这个引用还没有指向某个<br>对象，再任何引用使用前，必须为其指定一个对象，否则会报错。</p></li><li><p>基本数据类型在声明时系统会自动给它分配空间，而引用类型声明时只是分配了引用空间，<br>必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象，将一个数组赋值给另<br>一个数组时只是复制了一个引用，所以通过某一个数组所做的修改在另一个数组中也看的见。</p></li><li><p>虽然定义了boolean这种数据类型，但是只对它提供了非常有限的支持。在Java虚拟机中没有<br>任何供boolean值专用的字节码指令，Java语言表达式所操作的boolean值，在编译之后都使用Java<br>虚拟机中的int数据类型来代替，而boolean数组将会被编码成Java虚拟机的byte数组，每个元素<br>boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节，在数组中又是1个字<br>节。使用int的原因是，对于当下32位的处理器（CPU）来说，一次处理数据是32位（这里不是指的<br>是32&#x2F;64位系统，而是指CPU硬件层面），具有高效存取的特点。</p></li></ol><h3 id="4、标识符的命名规则。"><a href="#4、标识符的命名规则。" class="headerlink" title="4、标识符的命名规则。"></a>4、标识符的命名规则。</h3><p><strong>标识符的含义：</strong> 是指在程序中，我们自己定义的内容，譬如，类的名字，方法名称以及变量名称等<br>等，都是标识符。</p><p><strong>命名规则：（硬性要求）</strong> 标识符可以包含英文字母，0-9的数字，$以及_ 标识符不能以数字开头 标<br>识符不是关键字</p><p><strong>命名规范：（非硬性要求） 类名规范：</strong> 首字符大写，后面每个单词首字母大写（大驼峰式）。 变量<br>名规范：首字母小写，后面每个单词首字母大写（小驼峰式）。 方法名规范：同变量名。</p><h3 id="5、instanceof-关键字的作用"><a href="#5、instanceof-关键字的作用" class="headerlink" title="5、instanceof 关键字的作用"></a>5、instanceof 关键字的作用</h3><p><code>instanceof</code> 严格来说是Java中的一个双目运算符，用来测试一个对象是否为一个类的实例，用法<br>为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> obj <span class="keyword">instanceof</span> Class</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中 obj 为一个对象，Class 表示一个类或者一个接口，当 obj 为 Class 的对象，或者是其直接<br>或间接子类，或者是其接口的实现类，结果result 都返回 true，否则返回false。</p><p>注意：编译器会检查 obj 是否能转换成右边的class类型，如果不能转换则直接报错，如果不能<br>确定类型，则通过编译，具体看运行时定。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> System.out.println(i <span class="keyword">instanceof</span> Integer);<span class="comment">// 编译不通过  i必须是引用类型，不能是基本类型</span></span><br><span class="line">System.out.println(i <span class="keyword">instanceof</span> Object);<span class="comment">// 编译不通过</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">integer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">1</span>);</span><br><span class="line"> System.out.println(integer <span class="keyword">instanceof</span>  Integer);<span class="comment">// true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// false,在 JavaSE规范 中对 instanceof 运算符的规定就是：如果 obj 为 null，那么将返回 false。</span></span><br><span class="line">System.out.println(<span class="literal">null</span> <span class="keyword">instanceof</span> Object);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="6、Java自动装箱与拆箱"><a href="#6、Java自动装箱与拆箱" class="headerlink" title="6、Java自动装箱与拆箱"></a>6、Java自动装箱与拆箱</h3><p>装箱就是自动将基本数据类型转换为包装器类型（int–&gt;Integer）；调用方法：Integer的<br>valueOf(int) 方法</p><p>拆箱就是自动将包装器类型转换为基本数据类型（Integer–&gt;int）。调用方法：Integer的<br>intValue方法</p><p>在Java SE5之前，如果要生成一个数值为10的Integer对象，必须这样进行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">10</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>而在从Java SE5开始就提供了自动装箱的特性，如果要生成一个数值为10的Integer对象，只需要<br>这样就可以了：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>面试题1： 以下代码会输出什么？</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">         </span><br><span class="line">        <span class="type">Integer</span> <span class="variable">i1</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">i2</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">i3</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">i4</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">         </span><br><span class="line">        System.out.println(i1==i2);</span><br><span class="line">        System.out.println(i3==i4);</span><br><span class="line">    &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">true</span></span><br><span class="line"><span class="literal">false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>为什么会出现这样的结果？输出结果表明i1和i2指向的是同一个对象，而i3和i4指向的是不同的对<br>象。此时只需一看源码便知究竟，下面这段代码是Integer的valueOf方法的具体实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Integer <span class="title function_">valueOf</span><span class="params">(<span class="type">int</span> i)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(i &gt;= -<span class="number">128</span> &amp;&amp; i &lt;= IntegerCache.high)</span><br><span class="line">            <span class="keyword">return</span> IntegerCache.cache[i + <span class="number">128</span>];</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中IntegerCache类的实现为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">IntegerCache</span> &#123;</span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> high;</span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> Integer cache[];</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">static</span> &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">low</span> <span class="operator">=</span> -<span class="number">128</span>;</span><br><span class="line"> </span><br><span class="line">            <span class="comment">// high value may be configured by property</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> <span class="number">127</span>;</span><br><span class="line">            <span class="keyword">if</span> (integerCacheHighPropValue != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// Use Long.decode here to avoid invoking methods that</span></span><br><span class="line">                <span class="comment">// require Integer&#x27;s autoboxing cache to be initialized</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> Long.decode(integerCacheHighPropValue).intValue();</span><br><span class="line">                i = Math.max(i, <span class="number">127</span>);</span><br><span class="line">                <span class="comment">// Maximum array size is Integer.MAX_VALUE</span></span><br><span class="line">                h = Math.min(i, Integer.MAX_VALUE - -low);</span><br><span class="line">            &#125;</span><br><span class="line">            high = h;</span><br><span class="line"> </span><br><span class="line">            cache = <span class="keyword">new</span> <span class="title class_">Integer</span>[(high - low) + <span class="number">1</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> low;</span><br><span class="line">            <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">0</span>; k &lt; cache.length; k++)</span><br><span class="line">                cache[k] = <span class="keyword">new</span> <span class="title class_">Integer</span>(j++);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">private</span> <span class="title function_">IntegerCache</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从这2段代码可以看出，在通过valueOf方法创建Integer对象的时候，如果数值在[-128,127]之间，<br>便返回指向IntegerCache.cache中已经存在的对象的引用；否则创建一个新的Integer对象。</p><p>上面的代码中i1和i2的数值为100，因此会直接从cache中取已经存在的对象，所以i1和i2指向的是<br>同一个对象，而i3和i4则是分别指向不同的对象。</p><p><strong>面试题2：以下代码输出什么</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">         </span><br><span class="line">        <span class="type">Double</span> <span class="variable">i1</span> <span class="operator">=</span> <span class="number">100.0</span>;</span><br><span class="line">        <span class="type">Double</span> <span class="variable">i2</span> <span class="operator">=</span> <span class="number">100.0</span>;</span><br><span class="line">        <span class="type">Double</span> <span class="variable">i3</span> <span class="operator">=</span> <span class="number">200.0</span>;</span><br><span class="line">        <span class="type">Double</span> <span class="variable">i4</span> <span class="operator">=</span> <span class="number">200.0</span>;</span><br><span class="line">         </span><br><span class="line">        System.out.println(i1==i2);</span><br><span class="line">        System.out.println(i3==i4);</span><br><span class="line">    &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">false</span></span><br><span class="line"><span class="literal">false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>原因： 在某个范围内的整型数值的个数是有限的，而浮点数却不是。</p><h3 id="7、-重载和重写的区别"><a href="#7、-重载和重写的区别" class="headerlink" title="7、 重载和重写的区别"></a>7、 重载和重写的区别</h3><p><strong>重写(Override)</strong></p><p>从字面上看，重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子<br>类继承了父类原有的方法，但有时子类并不想原封不动的继承父类中的某个方法，所以在方法名，<br>参数列表，返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下， 对<br>方法体进行修改或重写，这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Father</span> &#123;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">        <span class="type">Son</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Son</span>();</span><br><span class="line">        s.sayHello();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Son</span> <span class="keyword">extends</span> <span class="title class_">Father</span> &#123;</span><br><span class="line">     <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">        System.out.println(<span class="string">&quot;hello by &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>重写 总结：</strong></p><ol><li>发生在父类与子类之间</li><li>方法名，参数列表，返回类型（除过子类中方法的返回类型是父类中返回类型的子类）必须相同</li><li>访问修饰符的限制一定要大于被重写方法的访问修饰符 （public&gt;protected&gt;default&gt;private)</li><li>重写方法一定不能抛出新的检查异常或者比被重写方法申 明更加宽泛的检查型异常</li></ol><p><strong>重载（Overload）</strong></p><p>在一个类中，同名的方法如果有不同的参数列表（参数类型不同、参数个数不同甚至是参数顺序不<br>同）则视为重载。同时，重载对返回类型没有要求，可以相同也可以不同，但不能通过返回类型是<br>否相同来判断重载。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Father</span> &#123;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">       <span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">       <span class="type">Father</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Father</span>();</span><br><span class="line">       s.sayHello();</span><br><span class="line">       s.sayHello(<span class="string">&quot;wintershii&quot;</span>);</span><br><span class="line"></span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">       System.out.println(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">(String name)</span> &#123;</span><br><span class="line">       System.out.println(<span class="string">&quot;Hello&quot;</span> + <span class="string">&quot; &quot;</span> + name);</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>重载 总结：</strong></p><ol><li>重载Overload是一个类中多态性的一种表现</li><li>重载要求同名方法的参数列表不同(参数类型，参数个数甚至是参数顺序)</li><li>重载的时候，返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准</li></ol>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/cdd12bd3/</id>
    <link href="https://luoyuanxiang.top/posts/cdd12bd3/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文系统介绍了Java语言的核心基础概念，包括语言特点、面向对象与面向过程区别、基本数据类型与封装类、命名规则、instanceof关键字、自动装箱与拆箱机制，以及重载与重写的区别，适合Java初学者系统学习。</summary>
    <title>Java基础核心知识点详解</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="tesseract" scheme="https://luoyuanxiang.top/tags/tesseract/"/>
    <category term="shell" scheme="https://luoyuanxiang.top/tags/shell/"/>
    <category term="Linux" scheme="https://luoyuanxiang.top/tags/Linux/"/>
    <content>
      <![CDATA[<h2 id="Linux-安装-Tesseract"><a href="#Linux-安装-Tesseract" class="headerlink" title="Linux 安装 Tesseract"></a>Linux 安装 Tesseract</h2><h3 id="下载需要的包"><a href="#下载需要的包" class="headerlink" title="下载需要的包"></a>下载需要的包</h3><h4 id="tesseract-需要单独下载"><a href="#tesseract-需要单独下载" class="headerlink" title="tesseract 需要单独下载"></a>tesseract 需要单独下载</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">https://github.com/tesseract-ocr/tesseract/releases/tag/5.3.3</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="下载leptonica"><a href="#下载leptonica" class="headerlink" title="下载leptonica"></a>下载leptonica</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget http://www.leptonica.org/source/leptonica-1.78.0.tar.gz</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><h4 id="解压leptonica"><a href="#解压leptonica" class="headerlink" title="解压leptonica"></a>解压leptonica</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tar -xvf leptonica-1.78.0.tar.gz</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="配置编译安装leptonica-进入到文件夹中"><a href="#配置编译安装leptonica-进入到文件夹中" class="headerlink" title="配置编译安装leptonica,进入到文件夹中"></a>配置编译安装leptonica,进入到文件夹中</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">./configure</span><br><span class="line">make</span><br><span class="line">make install</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="安装automake和libtool"><a href="#安装automake和libtool" class="headerlink" title="安装automake和libtool"></a>安装automake和libtool</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum -y install automake libtool</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="配置leptonica环境变量"><a href="#配置leptonica环境变量" class="headerlink" title="配置leptonica环境变量"></a>配置leptonica环境变量</h3><h4 id="修改profile-可以使用命令行或者直接编辑"><a href="#修改profile-可以使用命令行或者直接编辑" class="headerlink" title="修改profile,可以使用命令行或者直接编辑"></a>修改profile,可以使用命令行或者直接编辑</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/profile</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="在文件结尾添加"><a href="#在文件结尾添加" class="headerlink" title="在文件结尾添加"></a>在文件结尾添加</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export LD_LIBRARY_PATH=/usr/local/lib</span><br><span class="line">export LIBLEPT_HEADERSDIR=/usr/local/include</span><br><span class="line">export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="保存并退出文件"><a href="#保存并退出文件" class="headerlink" title="保存并退出文件"></a>保存并退出文件</h4><h4 id="执行以下命令使配置生效"><a href="#执行以下命令使配置生效" class="headerlink" title="执行以下命令使配置生效"></a>执行以下命令使配置生效</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">source /etc/profile</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="安装tesseract-ocr"><a href="#安装tesseract-ocr" class="headerlink" title="安装tesseract-ocr"></a>安装tesseract-ocr</h3><h4 id="安装依赖-1"><a href="#安装依赖-1" class="headerlink" title="安装依赖"></a>安装依赖</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum install -y centos-release-scl devtoolset-8-gcc*</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="切换当前会话中gcc版本为8"><a href="#切换当前会话中gcc版本为8" class="headerlink" title="切换当前会话中gcc版本为8"></a>切换当前会话中gcc版本为8</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">scl enable devtoolset-8 bash</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">yum install gcc-c++ libstdc++-devel</span><br><span class="line">tar -xvf tesseract-4.0.0.tar.gz</span><br><span class="line">cd ./tesseract-4.0.0</span><br><span class="line">./autogen.sh</span><br><span class="line">./configure</span><br><span class="line">make</span><br><span class="line">make install</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="测试是否安装成功-执行ldconfig"><a href="#测试是否安装成功-执行ldconfig" class="headerlink" title="测试是否安装成功,执行ldconfig"></a>测试是否安装成功,执行ldconfig</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tesseract --version</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/5fbf6f72/</id>
    <link href="https://luoyuanxiang.top/posts/5fbf6f72/"/>
    <link href="https://cdn.luoyuanxiang.top/cover-linux-tesseract-ocr.webp" rel="enclosure"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文详细介绍了在Linux系统中安装Tesseract OCR引擎的完整步骤，包括依赖包下载、环境变量配置、编译安装过程及最终版本验证方法，帮助用户顺利完成安装。</summary>
    <title>Linux系统安装Tesseract图文识别教程</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <category term="优化" scheme="https://luoyuanxiang.top/tags/%E4%BC%98%E5%8C%96/"/>
    <category term="性能" scheme="https://luoyuanxiang.top/tags/%E6%80%A7%E8%83%BD/"/>
    <category term="sql" scheme="https://luoyuanxiang.top/tags/sql/"/>
    <content>
      <![CDATA[<ol><li><p>对查询进行优化，要尽量避免全表扫描，首先应考虑在where及order by涉及的列上建立索引。</p></li><li><p>应尽量避免在where子句中对字段进行null值判断，否则将导致引擎放弃使用索引而进行全表扫描，如：</p></li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="keyword">is</span> <span class="keyword">null</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>最好不要给数据库留<code>NULL</code>，尽可能的使用<code>NOT NULL</code>填充数据库.<br>备注、描述、评论之类的可以设置为<code>NULL</code>，其他的，最好不要使用<code>NULL</code>。<br>不要以为<code>NULL</code>不需要空间，比如：<code>char(100)</code>型，在字段建立时，空间就固定了， 不管是否插入值（<code>NULL</code>也包含在内），都是占用 100个字符的空间的，如果是<code>varchar</code>这样的变长字段，<code>null</code>不占用空间。<br>可以在<code>num</code>上设置默认值0，确保表中num列没有null值，然后这样查询：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="operator">=</span> <span class="number">0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="3"><li>应尽量避免在<code>where</code>子句中使用<code>!=</code>或<code>&lt;&gt;</code>操作符，否则将引擎放弃使用索引而进行全表扫描。</li><li>应尽量避免在<code>where</code>子句中使用<code>or</code>来连接条件，如果一个字段有索引，一个字段没有索引，将导致引擎放弃使用索引而进行全表扫描，如：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num<span class="operator">=</span><span class="number">10</span> <span class="keyword">or</span> Name <span class="operator">=</span> <span class="string">&#x27;admin&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以这样查询：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="operator">=</span> <span class="number">10</span></span><br><span class="line"><span class="keyword">union</span> <span class="keyword">all</span></span><br><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> Name <span class="operator">=</span> <span class="string">&#x27;admin&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="5"><li><code>in</code>和<code>not in</code>也要慎用，否则会导致全表扫描，如：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="keyword">in</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对于连续的数值，能用 <code>between</code> 就不要用<code>in</code>了：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="keyword">between</span> <span class="number">1</span> <span class="keyword">and</span> <span class="number">3</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>很多时候用<code>exists</code>代替<code>in</code>是一个好的选择：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> num <span class="keyword">from</span> a <span class="keyword">where</span> num <span class="keyword">in</span>(<span class="keyword">select</span> num <span class="keyword">from</span> b)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>用下面的语句替换：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> num <span class="keyword">from</span> a <span class="keyword">where</span> <span class="keyword">exists</span>(<span class="keyword">select</span> <span class="number">1</span> <span class="keyword">from</span> b <span class="keyword">where</span> num<span class="operator">=</span>a.num)</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="6"><li>下面的查询也将导致全表扫描：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> name <span class="keyword">like</span> ‘<span class="operator">%</span>abc<span class="operator">%</span>’</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>若要提高效率，可以考虑全文检索。</p><ol start="7"><li>如果在<code>where</code>子句中使用参数，也会导致全表扫描。因为<code>SQL</code>只有在运行时才会解析局部变量，但优化程序不能将访问计划的选择推迟到运行时；它必须在编译时进行选择。然 而，如果在编译时建立访问计划，变量的值还是未知的，因而无法作为索引选择的输入项。如下面语句将进行全表扫描：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="operator">=</span> <span class="variable">@num</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以改为强制查询使用索引：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">with</span>(index(索引名)) <span class="keyword">where</span> num <span class="operator">=</span> <span class="variable">@num</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>应尽量避免在 <code>where</code> 子句中对字段进行表达式操作，这将导致引擎放弃使用索引而进行全表扫描。如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num<span class="operator">/</span><span class="number">2</span> <span class="operator">=</span> <span class="number">100</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>应改为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> num <span class="operator">=</span> <span class="number">100</span><span class="operator">*</span><span class="number">2</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="8"><li>应尽量避免在where子句中对字段进行函数操作，这将导致引擎放弃使用索引而进行全表扫描。如：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> <span class="built_in">substring</span>(name,<span class="number">1</span>,<span class="number">3</span>) <span class="operator">=</span> ’abc’       <span class="operator">-</span>–name以abc开头的id</span><br><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> datediff(<span class="keyword">day</span>,createdate,’<span class="number">2005</span><span class="number">-11</span><span class="number">-30</span>′) <span class="operator">=</span> <span class="number">0</span>    <span class="operator">-</span>–‘<span class="number">2005</span><span class="number">-11</span><span class="number">-30</span>’    <span class="comment">--生成的id</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>应改为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> name <span class="keyword">like</span> <span class="string">&#x27;abc%&#x27;</span></span><br><span class="line"><span class="keyword">select</span> id <span class="keyword">from</span> t <span class="keyword">where</span> createdate <span class="operator">&gt;=</span> <span class="string">&#x27;2005-11-30&#x27;</span> <span class="keyword">and</span> createdate <span class="operator">&lt;</span> <span class="string">&#x27;2005-12-1&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="9"><li>不要在<code>where</code>子句中的&#x3D;左边进行函数、算术运算或其他表达式运算，否则系统将可能无法正确使用索引。</li><li>在使用索引字段作为条件时，如果该索引是复合索引，那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引，否则该索引将不会被使用，并且应尽可能的让字段顺序与索引顺序相一致。</li><li>不要写一些没有意义的查询，如需要生成一个空表结构：</li></ol><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> col1,col2 <span class="keyword">into</span> #t <span class="keyword">from</span> t <span class="keyword">where</span> <span class="number">1</span><span class="operator">=</span><span class="number">0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这类代码不会返回任何结果集，但是会消耗系统资源的，应改成这样：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> #t(…)</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="12"><li><code>Update</code>语句，如果只更改1、2个字段，不要<code>Update</code>全部字段，否则频繁调用会引起明显的性能消耗，同时带来大量日志。</li><li>对于多张大数据量（这里几百条就算大了）的表<code>JOIN</code>，要先分页再<code>JOIN</code>，否则逻辑读会很高，性能很差。</li><li><code>select count(*) from table；</code>这样不带任何条件的<code>count</code>会引起全表扫描，并且没有任何业务意义，是一定要杜绝的。</li><li>索引并不是越多越好，索引固然可以提高相应的<code>select</code>的效率，但同时也降低了<code>insert</code>及<code>update</code>的效率，因为<code>insert</code>或<code>update</code>时有可能会重建索引，所以怎样建索引需要慎重考虑，视具体情况而定。一个表的索引数最好不要超过6个，若太多则应考虑一些不常使用到的列上建的索引是否有 必要。</li><li>应尽可能的避免更新<code>clustered</code>索引数据列，因为<code>clustered</code>索引数据列的顺序就是表记录的物理存储顺序，一旦该列值改变将导致整个表记录的顺序的调整，会耗费相当大的资源。若应用系统需要频繁更新<code>clustered</code>索引数据列，那么需要考虑是否应将该索引建为<code>clustered</code>索引。</li><li>尽量使用数字型字段，若只含数值信息的字段尽量不要设计为字符型，这会降低查询和连接的性能，并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符，而对于数字型而言只需要比较一次就够了。</li><li>尽可能的使用<code>varchar/nvarchar</code>代替<code>char/nchar</code>，因为首先变长字段存储空间小，可以节省存储空间，其次对于查询来说，在一个相对较小的字段内搜索效率显然要高些。</li><li>任何地方都不要使用<code>select * from t</code>用具体的字段列表代替*，不要返回用不到的任何字段。</li><li>尽量使用表变量来代替临时表。如果表变量包含大量数据，请注意索引非常有限（只有主键索引）。</li><li>避免频繁创建和删除临时表，以减少系统表资源的消耗。临时表并不是不可使用，适当地使用它们可以使某些例程更有效，例如，当需要重复引用大型表或常用表中的某个数据集时。但是，对于一次性事件， 最好使用导出表。</li><li>在新建临时表时，如果一次性插入数据量很大，那么可以使用<code>select into</code>代替<code>create table</code>，避免造成大量 <code>log</code> ，以提高速度；如果数据量不大，为了缓和系统表的资源，应先<code>create table</code>，然后<code>insert</code>。</li><li>如果使用到了临时表，在存储过程的最后务必将所有的临时表显式删除，先<code>truncate table</code>，然后<code>drop table</code>，这样可以避免系统表的较长时间锁定。</li><li>尽量避免使用游标，因为游标的效率较差，如果游标操作的数据超过1万行，那么就应该考虑改写。</li><li>使用基于游标的方法或临时表方法之前，应先寻找基于集的解决方案来解决问题，基于集的方法通常更有效。</li><li>与临时表一样，游标并不是不可使用。对小型数据集使用<code>FAST_FORWARD</code>游标通常要优于其他逐行处理方法，尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许，基于游标的方法和基于集的方法都可以尝试一下，看哪一种方法的效果更好。</li><li>在所有的存储过程和触发器的开始处设置<code>SET NOCOUNT ON</code>，在结束时设置<code>SET NOCOUNT OFF</code>。无需在执行存储过程和触发器的每个语句后向客户端发送<code>DONE_IN_PROC</code>消息。</li><li>尽量避免大事务操作，提高系统并发能力。</li><li>尽量避免向客户端返回大数据量，若数据量过大，应该考虑相应需求是否合理。</li></ol><blockquote><p>人所缺乏的不是才干而是志向，不是成功的能力而是勤劳的意志。 —— 部尔卫</p></blockquote>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/5569a14c/</id>
    <link href="https://luoyuanxiang.top/posts/5569a14c/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文总结了29条SQL查询性能优化建议，重点涵盖索引使用、避免全表扫描、NULL值处理、查询语句编写规范以及临时表与游标的合理使用，帮助开发者提升数据库操作效率。</summary>
    <title>SQL查询优化与索引使用指南</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="shell" scheme="https://luoyuanxiang.top/tags/shell/"/>
    <category term="Linux" scheme="https://luoyuanxiang.top/tags/Linux/"/>
    <content>
      <![CDATA[<h2 id="命令参数"><a href="#命令参数" class="headerlink" title="命令参数"></a>命令参数</h2><blockquote><p>[!NOTE] 示例</p><ul><li><code>-s|--start</code>：启动服务，后跟端口号</li><li><code>-stop</code>：停止服务，后跟端口号</li><li><code>-c|--copy</code>：备份并更新项目文件</li><li><code>-l|--log</code>：查看日志，后跟端口号</li><li>示例：<code>sh xxx.sh -s 12000</code> 启动端口为 12000 的服务</li></ul></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">项目名称</span></span><br><span class="line">APP_NAME=safirst-etmf-api-1.0.0.jar</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">根据端口号启动服务</span></span><br><span class="line">start_port() &#123;</span><br><span class="line">  echo &quot;准备启动服务，端口号：$PORT&quot;</span><br><span class="line">  pid=$(netstat -an | grep $PORT | awk &#x27;&#123;print $4&#125;&#x27;)</span><br><span class="line">  echo &quot;当前进程信息：$pid&quot;</span><br><span class="line">  if [[ -n $pid ]]; then</span><br><span class="line">    echo &quot;端口号 $PORT 的服务已在运行&quot;</span><br><span class="line">  else</span><br><span class="line">    echo &quot;正在启动服务...&quot;</span><br><span class="line">    nohup java -jar $APP_NAME --server.port=$&#123;PORT&#125; --logging.file.name=/mnt/nas/etmf/irzhd-logs-api-$PORT-105/spring.log &gt; /dev/null &amp;</span><br><span class="line">  fi</span><br><span class="line">  exit 1</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">备份并更新项目文件</span></span><br><span class="line">copy_c() &#123;</span><br><span class="line">  FILE_NAME=$(date -d last-day +%Y%m%d)</span><br><span class="line">  echo &quot;开始备份项目...&quot;</span><br><span class="line">  cp $APP_NAME $&#123;APP_NAME&#125;_$&#123;FILE_NAME&#125;</span><br><span class="line">  echo &quot;备份完成：$&#123;APP_NAME&#125;_$&#123;FILE_NAME&#125;&quot;</span><br><span class="line">  echo &quot;正在更新项目文件...&quot;</span><br><span class="line">  cp /mnt/nas/jar/$APP_NAME .</span><br><span class="line">  echo &quot;项目更新成功&quot;</span><br><span class="line">  exit 1</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">停止指定端口的服务</span></span><br><span class="line">stop_s() &#123;</span><br><span class="line">  echo &quot;正在停止端口 $PORT 的服务...&quot;</span><br><span class="line">  ps -ef | grep $PORT | grep -v grep | awk &#x27;&#123;print $2&#125;&#x27; | xargs kill -9</span><br><span class="line">  echo &quot;服务已停止&quot;</span><br><span class="line">  exit 1</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看日志</span></span><br><span class="line">log_port() &#123;</span><br><span class="line">  tail -f /mnt/nas/etmf/irzhd-logs-api-$PORT-105/spring.log</span><br><span class="line">  exit 1</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">使用说明</span></span><br><span class="line">usage() &#123;</span><br><span class="line">  echo &quot;Usage: $&#123;0&#125; [-s|--start] [-stop] [-c|--copy] [-l|--log]&quot; 1&gt;&amp;2</span><br><span class="line">  exit 1</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">参数解析</span></span><br><span class="line">while [[ $# -gt 0 ]]; do</span><br><span class="line">  key=$&#123;1&#125;</span><br><span class="line">  case $&#123;key&#125; in</span><br><span class="line">    -s|--start)</span><br><span class="line">      PORT=$&#123;2&#125;</span><br><span class="line">      if [[ ! -n $PORT ]]; then</span><br><span class="line">        echo &quot;请提供端口号，例如：-s 12000&quot;</span><br><span class="line">        exit 1</span><br><span class="line">      else</span><br><span class="line">        echo &quot;正在启动端口号：$PORT&quot;</span><br><span class="line">        shift 2</span><br><span class="line">        start_port</span><br><span class="line">      fi</span><br><span class="line">      ;;</span><br><span class="line">    -c|--copy)</span><br><span class="line">      copy_c</span><br><span class="line">      shift 2</span><br><span class="line">      ;;</span><br><span class="line">    -stop)</span><br><span class="line">      PORT=$&#123;2&#125;</span><br><span class="line">      if [[ ! -n $PORT ]]; then</span><br><span class="line">        echo &quot;请提供端口号，例如：-stop 12000&quot;</span><br><span class="line">        exit 1</span><br><span class="line">      else</span><br><span class="line">        stop_s</span><br><span class="line">        shift 2</span><br><span class="line">      fi</span><br><span class="line">      ;;</span><br><span class="line">    -l|--log)</span><br><span class="line">      PORT=$&#123;2&#125;</span><br><span class="line">      if [[ ! -n $PORT ]]; then</span><br><span class="line">        echo &quot;请提供端口号，例如：-l 12000&quot;</span><br><span class="line">        exit 1</span><br><span class="line">      else</span><br><span class="line">        log_port</span><br><span class="line">        shift 2</span><br><span class="line">      fi</span><br><span class="line">      ;;</span><br><span class="line">    *)</span><br><span class="line">      usage</span><br><span class="line">      shift</span><br><span class="line">      ;;</span><br><span class="line">  esac</span><br><span class="line">done</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/bfde2501/</id>
    <link href="https://luoyuanxiang.top/posts/bfde2501/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文详细介绍了用于启动、停止、备份和查看日志的Shell脚本命令参数，包括-s/--start启动服务、-stop停止服务、-c/--copy备份项目以及-l/--log查看日志，各命令均需指定端口号操作。</summary>
    <title>Shell脚本命令参数使用指南</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="基础" scheme="https://luoyuanxiang.top/tags/%E5%9F%BA%E7%A1%80/"/>
    <category term="Java" scheme="https://luoyuanxiang.top/tags/Java/"/>
    <category term="spring" scheme="https://luoyuanxiang.top/tags/spring/"/>
    <category term="spring boot" scheme="https://luoyuanxiang.top/tags/spring-boot/"/>
    <category term="shiro" scheme="https://luoyuanxiang.top/tags/shiro/"/>
    <category term="jwt" scheme="https://luoyuanxiang.top/tags/jwt/"/>
    <content>
      <![CDATA[<p>一、思路</p><p><code>shiro</code> 用来认证用户及权限控制，<code>jwt</code>用来生成一个<code>token</code>，暂存用户信息。</p><p>为什么不使用<code>session</code>而使用<code>jwt</code>？传统情况下是只有一个服务器，用户登陆后将一些信息以session的形式存储服务器上，</p><p>然后将<code>sessionid</code>存储在本地<code>cookie</code>中，当用户下次请求时将会将<code>sessionid</code>传递给服务器，用于确认身份。</p><p>但如果是分布式的情况下会出现问题，在服务器集群中，需要一个<code>session</code>数据库来存储每一个session，提供给集群中所有服务使用，且无法跨域(多个Ip)使用。</p><p>而<code>jwt</code>是生成一个<code>token</code>存储在客户端，每次请求将其存储在<code>header</code>中，解决了跨域，且可以通过自定义的方法进行验证，解决了分布式验证的问题。</p><p>缺点：无法在服务器注销、比<code>sessionid</code>大占带宽、一次性（想修改里面的内容，就必须签发一个新的<code>jwt</code>）</p><p>二、废话不多说上代码</p><p><code>pom.xml</code></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.spring.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.1.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-spring<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-aop<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 工具 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.hutool<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hutool-all<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.2.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 密码加密 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.ulisesbocchio<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jasypt-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.1.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>fastjson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.62<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- mybatis-plus --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.baomidou<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-plus-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.3.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 数据源 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>druid-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.1.21<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- xss过滤组件 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.jsoup<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jsoup<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.9.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- restful api 文档 swagger2 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.springfox<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>springfox-swagger2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.9.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.xiaoymin<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>swagger-bootstrap-ui<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.9.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>重构<code>token</code>生成继承 <code>AuthenticationToken</code> 类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.token;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lombok.Data;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationToken;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">* 自定义token</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/19 17:06</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="meta">@Data</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Oauth2Token</span> <span class="keyword">implements</span> <span class="title class_">AuthenticationToken</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">8585428037102822625L</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * json web token值</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="keyword">private</span> String jwt;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">Oauth2Token</span><span class="params">(String jwt)</span> &#123;</span><br><span class="line">  <span class="built_in">this</span>.jwt = jwt;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * jwt</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> jwt</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> Object <span class="title function_">getPrincipal</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">this</span>.jwt;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 返回jwt</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> jwt</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> Object <span class="title function_">getCredentials</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">this</span>.jwt;</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>自定义过滤器，继承 <code>AuthenticatingFilter</code> 类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.shiro.filter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.hutool.core.util.StrUtil;</span><br><span class="line"><span class="keyword">import</span> cn.hutool.json.JSONUtil;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSON;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.token.Oauth2Token;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.util.JsonWebTokenUtil;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.vo.JwtAccount;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.properties.VjsbProperties;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.util.AjaxResult;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.util.IpUtil;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.SignatureAlgorithm;</span><br><span class="line"><span class="keyword">import</span> lombok.Setter;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationException;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationToken;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.filter.authc.AuthenticatingFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.util.WebUtils;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.StringRedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.http.HttpStatus;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletResponse;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"><span class="keyword">import</span> java.util.UUID;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">* 自定义过滤器配置</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/19 17:34</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="meta">@Slf4j</span></span><br><span class="line">  <span class="meta">@Setter</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Oauth2Filter</span> <span class="keyword">extends</span> <span class="title class_">AuthenticatingFilter</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">expiredJwt</span> <span class="operator">=</span> <span class="string">&quot;expiredJwt&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> StringRedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> VjsbProperties properties;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 对跨域提供支持</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(ServletRequest request, ServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">      <span class="type">HttpServletRequest</span> <span class="variable">httpServletRequest</span> <span class="operator">=</span> (HttpServletRequest) request;</span><br><span class="line">      <span class="type">HttpServletResponse</span> <span class="variable">httpServletResponse</span> <span class="operator">=</span> (HttpServletResponse) response;</span><br><span class="line">      httpServletResponse.setHeader(<span class="string">&quot;Access-control-Allow-Origin&quot;</span>, httpServletRequest.getHeader(<span class="string">&quot;Origin&quot;</span>));</span><br><span class="line">      httpServletResponse.setHeader(<span class="string">&quot;Access-Control-Allow-Methods&quot;</span>, <span class="string">&quot;GET,POST,OPTIONS,PUT,DELETE&quot;</span>);</span><br><span class="line">      httpServletResponse.setHeader(<span class="string">&quot;Access-Control-Allow-Headers&quot;</span>, httpServletRequest.getHeader(<span class="string">&quot;Access-Control-Request-Headers&quot;</span>));</span><br><span class="line">      <span class="comment">// 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态</span></span><br><span class="line">      <span class="keyword">if</span> (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) &#123;</span><br><span class="line">      httpServletResponse.setStatus(HttpStatus.OK.value());</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">super</span>.preHandle(request, response);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 先执行：isAccessAllowed 再执行onAccessDenied</span></span><br><span class="line"><span class="comment">    * 如果返回true的话，就直接返回交给下一个filter进行处理。</span></span><br><span class="line"><span class="comment">    * 如果返回false的话，回往下执行onAccessDenied</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> request     the incoming &lt;code&gt;ServletRequest&lt;/code&gt;</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> response    the outgoing &lt;code&gt;ServletResponse&lt;/code&gt;</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> &lt;code&gt;true&lt;/code&gt; if the request should proceed through the filter normally, &lt;code&gt;false&lt;/code&gt; if the</span></span><br><span class="line"><span class="comment">    * request should be processed by this filter&#x27;s</span></span><br><span class="line"><span class="comment">    * &#123;<span class="doctag">@link</span> #onAccessDenied(ServletRequest, ServletResponse, Object)&#125; method instead.</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isAccessAllowed</span><span class="params">(ServletRequest request, ServletResponse response, Object mappedValue)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * onAccessDenied：表示当访问拒绝时是否已经处理了；如果返回true表示需要继续处理；</span></span><br><span class="line"><span class="comment">    * 如果返回false表示该拦截器实例已经处理了，将直接返回即可。</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> request  the incoming &lt;code&gt;ServletRequest&lt;/code&gt;</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> response the outgoing &lt;code&gt;ServletResponse&lt;/code&gt;</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> &lt;code&gt;true&lt;/code&gt; if the request should continue to be processed; false if the subclass will</span></span><br><span class="line"><span class="comment">    * handle/render the response directly.</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@throws</span> Exception if there is an error processing the request.</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">onAccessDenied</span><span class="params">(ServletRequest request, ServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">      <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> getRequestToken((HttpServletRequest) request);</span><br><span class="line">      <span class="keyword">if</span> (StrUtil.isBlank(token)) &#123;</span><br><span class="line">      AjaxResult.responseWrite(JSON.toJSONString(AjaxResult.success(<span class="string">&quot;无权限访问&quot;</span>, <span class="number">1007</span>, <span class="literal">null</span>)), response);</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> executeLogin(request, response);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">protected</span> AuthenticationToken <span class="title function_">createToken</span><span class="params">(ServletRequest request, ServletResponse response)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">  <span class="comment">// 这个创建token是在登录完成之后，去调用控制层时调用的，也就是要有token的时候，才会调用这个方法</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Oauth2Token</span>(getRequestToken(WebUtils.toHttp(request)));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取请求的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">getRequestToken</span><span class="params">(HttpServletRequest httpRequest)</span> &#123;</span><br><span class="line">        <span class="comment">//从header中获取token</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> httpRequest.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//如果header中不存在token，则从参数中获取token</span></span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isBlank(token)) &#123;</span><br><span class="line">            token = httpRequest.getParameter(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> token;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 登录失败处理</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> token token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e 异常</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> request 1</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> response 2</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> boolean</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">onLoginFailure</span><span class="params">(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response)</span> &#123;</span><br><span class="line">        <span class="comment">//处理登录失败的异常</span></span><br><span class="line">        <span class="type">Throwable</span> <span class="variable">throwable</span> <span class="operator">=</span> e.getCause() == <span class="literal">null</span> ? e : e.getCause();</span><br><span class="line">        <span class="type">PrintWriter</span> <span class="variable">writer</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            writer = WebUtils.toHttp(response).getWriter();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException ignored) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">assert</span> writer !=<span class="literal">null</span>;</span><br><span class="line">        <span class="comment">// 这里做token验证处理，在验证器中验证token</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getMessage();</span><br><span class="line">        <span class="comment">// 令牌过期</span></span><br><span class="line">        <span class="keyword">if</span> (expiredJwt.equals(message)) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> JsonWebTokenUtil.parseJwtPayload(token.getCredentials().toString());</span><br><span class="line">            <span class="type">JwtAccount</span> <span class="variable">jwtAccount</span> <span class="operator">=</span> JSONUtil.toBean(jwt, JwtAccount.class);</span><br><span class="line">            <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;JWT-SESSION-&quot;</span> + IpUtil.getIpFromRequest((HttpServletRequest) request).toUpperCase() + jwtAccount.getSub());</span><br><span class="line">            <span class="keyword">if</span> (s != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 重新申请新的JWT</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">newJwt</span> <span class="operator">=</span> JsonWebTokenUtil.createToken(UUID.randomUUID().toString(), jwtAccount.getSub(),</span><br><span class="line">                        <span class="string">&quot;token-server&quot;</span>, jwtAccount.getPassword(), properties.getExpire(), SignatureAlgorithm.HS512);</span><br><span class="line">                <span class="comment">// 将签发的JWT存储到Redis： &#123;JWT-SESSION-&#123;appID&#125; , jwt&#125;</span></span><br><span class="line">                redisTemplate.opsForValue().set(<span class="string">&quot;JWT-SESSION-&quot;</span> + IpUtil.getIpFromRequest((HttpServletRequest) request) + <span class="string">&quot;_&quot;</span> + jwtAccount.getSub(), newJwt, properties.getExpire() &lt;&lt; <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">                writer.print(JSON.toJSONString(AjaxResult.success(<span class="string">&quot;刷新令牌&quot;</span>, <span class="number">1006</span>, newJwt)));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                writer.print(JSON.toJSONString(AjaxResult.success(<span class="string">&quot;令牌无效！&quot;</span>, <span class="number">1008</span>, <span class="literal">null</span>)));</span><br><span class="line">            &#125;</span><br><span class="line">            writer.flush();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        writer.print(JSON.toJSONString(AjaxResult.success(message, <span class="number">1008</span>, <span class="literal">null</span>)));</span><br><span class="line">        writer.flush();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>配置<code>config</code></p><p><code>shiroFilter</code>管理</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.shiro.filter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.holder.SpringContextHolder;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.properties.VjsbProperties;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.spring.web.ShiroFilterFactoryBean;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.servlet.AbstractShiroFilter;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.StringRedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.annotation.Resource;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.Filter;</span><br><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">* Shiro Filter 管理器</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/20 11:00</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="meta">@Slf4j</span></span><br><span class="line">  <span class="meta">@Component</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShiroFilterChainManager</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Resource</span></span><br><span class="line">  <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Resource</span></span><br><span class="line">  <span class="keyword">private</span> VjsbProperties vjsbProperties;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 初始化获取过滤链</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.util.Map&lt;java.lang.String, javax.servlet.Filter&gt;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, Filter&gt; <span class="title function_">initGetFilters</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        Map&lt;String, Filter&gt; filters = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>&lt;&gt;();</span><br><span class="line">        <span class="type">Oauth2Filter</span> <span class="variable">jwtFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Oauth2Filter</span>();</span><br><span class="line">        jwtFilter.setRedisTemplate(stringRedisTemplate);</span><br><span class="line">        jwtFilter.setProperties(vjsbProperties);</span><br><span class="line">        filters.put(<span class="string">&quot;oauth2&quot;</span>, jwtFilter);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> filters;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 初始化获取过滤链规则</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.util.Map&lt;java.lang.String, java.lang.String&gt;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, String&gt; <span class="title function_">initGetFilterChain</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        Map&lt;String, String&gt; filterChain = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>&lt;&gt;();</span><br><span class="line">        <span class="comment">// -------------anon 默认过滤器忽略的URL</span></span><br><span class="line">        List&lt;String&gt; defaultAnon = Arrays.asList(<span class="string">&quot;/css/**&quot;</span>, <span class="string">&quot;/js/**&quot;</span>, <span class="string">&quot;/login&quot;</span>);</span><br><span class="line">        defaultAnon.forEach(ignored -&gt; filterChain.put(ignored, <span class="string">&quot;anon&quot;</span>));</span><br><span class="line">        <span class="comment">// -------------auth 默认需要认证过滤器的URL 走auth--PasswordFilter</span></span><br><span class="line">        List&lt;String&gt; defaultAuth = Collections.singletonList(<span class="string">&quot;/**&quot;</span>);</span><br><span class="line">        defaultAuth.forEach(auth -&gt; filterChain.put(auth, <span class="string">&quot;oauth2&quot;</span>));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> filterChain;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 动态重新加载过滤链规则</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reloadFilterChain</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ShiroFilterFactoryBean</span> <span class="variable">shiroFilterFactoryBean</span> <span class="operator">=</span> SpringContextHolder.getBean(ShiroFilterFactoryBean.class);</span><br><span class="line">        <span class="type">AbstractShiroFilter</span> <span class="variable">abstractShiroFilter</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            abstractShiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();</span><br><span class="line">            <span class="keyword">assert</span> abstractShiroFilter != <span class="literal">null</span>;</span><br><span class="line">            <span class="type">PathMatchingFilterChainResolver</span> <span class="variable">filterChainResolver</span> <span class="operator">=</span> (PathMatchingFilterChainResolver) abstractShiroFilter.getFilterChainResolver();</span><br><span class="line">            <span class="type">DefaultFilterChainManager</span> <span class="variable">filterChainManager</span> <span class="operator">=</span> (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();</span><br><span class="line">            filterChainManager.getFilterChains().clear();</span><br><span class="line">            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();</span><br><span class="line">            shiroFilterFactoryBean.setFilterChainDefinitionMap(<span class="built_in">this</span>.initGetFilterChain());</span><br><span class="line">            shiroFilterFactoryBean.getFilterChainDefinitionMap().forEach(filterChainManager::createChain);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(e.getMessage(), e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>shiro</code>配置类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.shiro.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.shiro.filter.ShiroFilterChainManager;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.shiro.realm.Oauth2Realm;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.SecurityUtils;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.mgt.DefaultSessionStorageEvaluator;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.mgt.DefaultSubjectDAO;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.mgt.SecurityManager;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.session.mgt.DefaultSessionManager;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.spring.web.ShiroFilterFactoryBean;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.mgt.DefaultWebSecurityManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">* shiro 权限配置</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/19 15:17</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="meta">@Slf4j</span></span><br><span class="line">  <span class="meta">@Configuration</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShiroConfiguration</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Bean</span></span><br><span class="line">  <span class="keyword">public</span> ShiroFilterFactoryBean <span class="title function_">shiroFilterFactoryBean</span><span class="params">(SecurityManager securityManager, ShiroFilterChainManager filterChainManager)</span> &#123;</span><br><span class="line">  <span class="type">ShiroFilterFactoryBean</span> <span class="variable">shiroFilterFactoryBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ShiroFilterFactoryBean</span>();</span><br><span class="line">  shiroFilterFactoryBean.setSecurityManager(securityManager);</span><br><span class="line">  shiroFilterFactoryBean.setFilters(filterChainManager.initGetFilters());</span><br><span class="line">  shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainManager.initGetFilterChain());</span><br><span class="line">  <span class="keyword">return</span> shiroFilterFactoryBean;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Bean</span></span><br><span class="line">  <span class="keyword">public</span> SecurityManager <span class="title function_">securityManager</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="type">DefaultWebSecurityManager</span> <span class="variable">securityManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultWebSecurityManager</span>();</span><br><span class="line">  securityManager.setRealm(jwtRealm());</span><br><span class="line"></span><br><span class="line">       log.info(<span class="string">&quot;设置sessionManager禁用掉会话调度器&quot;</span>);</span><br><span class="line">       securityManager.setSessionManager(sessionManager());</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 无状态subjectFactory设置</span></span><br><span class="line">       <span class="type">DefaultSessionStorageEvaluator</span> <span class="variable">evaluator</span> <span class="operator">=</span> (DefaultSessionStorageEvaluator) ((DefaultSubjectDAO) securityManager.getSubjectDAO()).getSessionStorageEvaluator();</span><br><span class="line">       evaluator.setSessionStorageEnabled(Boolean.FALSE);</span><br><span class="line">       <span class="type">StatelessDefaultSubjectFactory</span> <span class="variable">subjectFactory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StatelessDefaultSubjectFactory</span>();</span><br><span class="line">       securityManager.setSubjectFactory(subjectFactory);</span><br><span class="line"></span><br><span class="line">       SecurityUtils.setSecurityManager(securityManager);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">return</span> securityManager;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Bean</span></span><br><span class="line">  <span class="keyword">public</span> Oauth2Realm <span class="title function_">jwtRealm</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Oauth2Realm</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * session管理器</span></span><br><span class="line"><span class="comment">    * sessionManager通过sessionValidationSchedulerEnabled禁用掉会话调度器，</span></span><br><span class="line"><span class="comment">    * 因为我们禁用掉了会话，所以没必要再定期过期会话了。</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> 1</span></span><br><span class="line"><span class="comment">      */</span></span><br><span class="line">      <span class="meta">@Bean</span></span><br><span class="line">      <span class="keyword">public</span> DefaultSessionManager <span class="title function_">sessionManager</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="type">DefaultSessionManager</span> <span class="variable">sessionManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultSessionManager</span>();</span><br><span class="line">      sessionManager.setSessionValidationSchedulerEnabled(Boolean.FALSE);</span><br><span class="line">      <span class="keyword">return</span> sessionManager;</span><br><span class="line">      &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">StatelessDefaultSubjectFactory</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.shiro.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.subject.Subject;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.subject.SubjectContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.web.mgt.DefaultWebSubjectFactory;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/20 16:24</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StatelessDefaultSubjectFactory</span> <span class="keyword">extends</span> <span class="title class_">DefaultWebSubjectFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> Subject <span class="title function_">createSubject</span><span class="params">(SubjectContext context)</span> &#123;</span><br><span class="line">  <span class="comment">//不创建session</span></span><br><span class="line">  context.setSessionCreationEnabled(<span class="literal">false</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">super</span>.createSubject(context);</span><br><span class="line">  &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>token</code>工具生成类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.util;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.vo.JwtAccount;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.*;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.impl.DefaultHeader;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.impl.DefaultJwsHeader;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.impl.TextCodec;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.lang.Assert;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.CollectionUtils;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.StringUtils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.xml.bind.DatatypeConverter;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* &lt;p&gt;</span></span><br><span class="line"><span class="comment">* token生成器</span></span><br><span class="line"><span class="comment">* &lt;/p&gt;</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> luoyuanxiang &lt;p&gt;luoyuanxiang.github.io&lt;/p&gt;</span></span><br><span class="line"><span class="comment">* <span class="doctag">@since</span> 2020/3/19 15:37</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JsonWebTokenUtil</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">SECRET_KEY</span> <span class="operator">=</span> <span class="string">&quot;?::4343fdf4fdf6cvf):&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ObjectMapper</span> <span class="variable">MAPPER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">COUNT_2</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">CompressionCodecResolver</span> <span class="variable">codecResolver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultCompressionCodecResolver</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">JsonWebTokenUtil</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * json web token 签发</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id          令牌ID</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> subject     用户ID</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> issuer      签发人</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> period      有效时间(秒)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> password    用户密码</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> algorithm   加密算法</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.lang.String</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">createToken</span><span class="params">(String id, String subject, String issuer, String password, Long period, SignatureAlgorithm algorithm)</span> &#123;</span><br><span class="line">        <span class="comment">// 当前时间戳</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">currentTimeMillis</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="comment">// 秘钥</span></span><br><span class="line">        <span class="type">byte</span>[] secreKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);</span><br><span class="line">        <span class="type">JwtBuilder</span> <span class="variable">jwtBuilder</span> <span class="operator">=</span> Jwts.builder();</span><br><span class="line">        Optional.ofNullable(id)</span><br><span class="line">                .ifPresent(i-&gt; &#123;</span><br><span class="line">                    jwtBuilder.setId(id);</span><br><span class="line">                &#125;);</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.isEmpty(id)) &#123;</span><br><span class="line">            jwtBuilder.setId(id);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.isEmpty(subject)) &#123;</span><br><span class="line">            jwtBuilder.setSubject(subject);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.isEmpty(issuer)) &#123;</span><br><span class="line">            jwtBuilder.setIssuer(issuer);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.isEmpty(password)) &#123;</span><br><span class="line">            jwtBuilder.claim(<span class="string">&quot;password&quot;</span>, password);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 设置签发时间</span></span><br><span class="line">        jwtBuilder.setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>(currentTimeMillis));</span><br><span class="line">        <span class="comment">// 设置到期时间</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="literal">null</span> != period) &#123;</span><br><span class="line">            jwtBuilder.setExpiration(<span class="keyword">new</span> <span class="title class_">Date</span>(currentTimeMillis + period * <span class="number">1000</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 压缩，可选GZIP</span></span><br><span class="line">        jwtBuilder.compressWith(CompressionCodecs.DEFLATE);</span><br><span class="line">        <span class="comment">// 加密设置</span></span><br><span class="line">        jwtBuilder.signWith(algorithm, secreKeyBytes);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> jwtBuilder.compact();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解析JWT的Payload</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">parseJwtPayload</span><span class="params">(String jwt)</span> &#123;</span><br><span class="line">        Assert.hasText(jwt, <span class="string">&quot;JWT String argument cannot be null or empty.&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">base64UrlEncodedHeader</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">base64UrlEncodedPayload</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">base64UrlEncodedDigest</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">delimiterCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">128</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> c : jwt.toCharArray()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (c == <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">                <span class="type">CharSequence</span> <span class="variable">tokenSeq</span> <span class="operator">=</span> io.jsonwebtoken.lang.Strings.clean(sb);</span><br><span class="line">                <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> tokenSeq != <span class="literal">null</span> ? tokenSeq.toString() : <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (delimiterCount == <span class="number">0</span>) &#123;</span><br><span class="line">                    base64UrlEncodedHeader = token;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (delimiterCount == <span class="number">1</span>) &#123;</span><br><span class="line">                    base64UrlEncodedPayload = token;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                delimiterCount++;</span><br><span class="line">                sb.setLength(<span class="number">0</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                sb.append(c);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (delimiterCount != COUNT_2) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="string">&quot;JWT strings must contain exactly 2 period characters. Found: &quot;</span> + delimiterCount;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MalformedJwtException</span>(msg);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (sb.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            base64UrlEncodedDigest = sb.toString();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (base64UrlEncodedPayload == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MalformedJwtException</span>(<span class="string">&quot;JWT string &#x27;&quot;</span> + jwt + <span class="string">&quot;&#x27; is missing a body/payload.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// =============== Header =================</span></span><br><span class="line">        <span class="type">Header</span> <span class="variable">header</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">CompressionCodec</span> <span class="variable">compressionCodec</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (base64UrlEncodedHeader != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">origValue</span> <span class="operator">=</span> TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);</span><br><span class="line">            Map&lt;String, Object&gt; m = readValue(origValue);</span><br><span class="line">            <span class="keyword">if</span> (base64UrlEncodedDigest != <span class="literal">null</span>) &#123;</span><br><span class="line">                header = <span class="keyword">new</span> <span class="title class_">DefaultJwsHeader</span>(m);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                header = <span class="keyword">new</span> <span class="title class_">DefaultHeader</span>(m);</span><br><span class="line">            &#125;</span><br><span class="line">            compressionCodec = codecResolver.resolveCompressionCodec(header);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// =============== Body =================</span></span><br><span class="line">        String payload;</span><br><span class="line">        <span class="keyword">if</span> (compressionCodec != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">byte</span>[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));</span><br><span class="line">            payload = <span class="keyword">new</span> <span class="title class_">String</span>(decompressed, io.jsonwebtoken.lang.Strings.UTF_8);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> payload;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 验签JWT</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> jwt    json web token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> appKey key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> JwtAccount</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> ExpiredJwtException      异常</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> UnsupportedJwtException  异常</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> MalformedJwtException    异常</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> SignatureException       异常</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IllegalArgumentException 异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> JwtAccount <span class="title function_">parseJwt</span><span class="params">(String jwt, String appKey)</span> <span class="keyword">throws</span> ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException &#123;</span><br><span class="line">        <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> Jwts.parser()</span><br><span class="line">                .setSigningKey(DatatypeConverter.parseBase64Binary(appKey))</span><br><span class="line">                .parseClaimsJws(jwt)</span><br><span class="line">                .getBody();</span><br><span class="line">        <span class="type">JwtAccount</span> <span class="variable">jwtAccount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JwtAccount</span>();</span><br><span class="line">        <span class="comment">// 令牌ID</span></span><br><span class="line">        jwtAccount.setJti(claims.getId())</span><br><span class="line">                <span class="comment">// 客户标识</span></span><br><span class="line">                .setSub(claims.getSubject())</span><br><span class="line">                <span class="comment">// 签发者</span></span><br><span class="line">                .setIss(claims.getIssuer())</span><br><span class="line">                <span class="comment">// 签发时间</span></span><br><span class="line">                .setIat(claims.getIssuedAt().getTime())</span><br><span class="line">                .setExp(claims.getExpiration().getTime())</span><br><span class="line">                <span class="comment">// 密码</span></span><br><span class="line">                .setPassword(claims.get(<span class="string">&quot;password&quot;</span>, String.class));</span><br><span class="line">        <span class="keyword">return</span> jwtAccount;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * description 从json数据中读取格式化map</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> val 1</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.util.Map&lt;java.lang.String, java.lang.Object&gt;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Map&lt;String, Object&gt; <span class="title function_">readValue</span><span class="params">(String val)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> MAPPER.readValue(val, Map.class);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MalformedJwtException</span>(<span class="string">&quot;Unable to read JSON value: &quot;</span> + val, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 分割字符串进SET</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Set&lt;String&gt; <span class="title function_">split</span><span class="params">(String str)</span> &#123;</span><br><span class="line"></span><br><span class="line">        Set&lt;String&gt; set = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(str)) &#123;</span><br><span class="line">            <span class="keyword">return</span> set;</span><br><span class="line">        &#125;</span><br><span class="line">        set.addAll(CollectionUtils.arrayToList(str.split(<span class="string">&quot;,&quot;</span>)));</span><br><span class="line">        <span class="keyword">return</span> set;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>到此项目差不多搞定</p><p>项目源码：<a href="https://gitee.com/luoyuanxiang/vjsb">源码</a>好的，我们继续完善这个基于 Shiro + JWT 的认证授权系统。接下来将补充核心的领域（Realm）实现、登录控制器以及一些必要的配置和工具类。</p><hr><h3 id="三、核心实现"><a href="#三、核心实现" class="headerlink" title="三、核心实现"></a>三、核心实现</h3><h4 id="1-自定义-Realm（Oauth2Realm）"><a href="#1-自定义-Realm（Oauth2Realm）" class="headerlink" title="1. 自定义 Realm（Oauth2Realm）"></a>1. 自定义 Realm（<code>Oauth2Realm</code>）</h4><p>Realm 是 Shiro 的核心组件，用于处理认证（登录）和授权（权限检查）。我们需要自定义一个 Realm 来处理 JWT Token。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.shiro.realm;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.shiro.token.Oauth2Token;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.util.JsonWebTokenUtil;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.vo.JwtAccount;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.properties.VjsbProperties;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationException;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationInfo;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationToken;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.SimpleAuthenticationInfo;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authz.AuthorizationInfo;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authz.SimpleAuthorizationInfo;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.realm.AuthorizingRealm;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.subject.PrincipalCollection;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.annotation.Resource;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;</span></span><br><span class="line"><span class="comment"> * 自定义 Realm，处理 JWT Token 的认证与授权</span></span><br><span class="line"><span class="comment"> * &lt;/p&gt;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Oauth2Realm</span> <span class="keyword">extends</span> <span class="title class_">AuthorizingRealm</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> VjsbProperties vjsbProperties;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 限定此 Realm 只支持我们自定义的 Oauth2Token 类型</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supports</span><span class="params">(AuthenticationToken token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> token <span class="keyword">instanceof</span> Oauth2Token;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 授权方法：当用户访问需要权限的接口时，会调用此方法</span></span><br><span class="line"><span class="comment">     * 由于我们的权限信息也存储在 JWT 中，可以直接解析，无需额外查询数据库（除非需要实时权限）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> AuthorizationInfo <span class="title function_">doGetAuthorizationInfo</span><span class="params">(PrincipalCollection principals)</span> &#123;</span><br><span class="line">        <span class="comment">// 从 principals 中获取主身份信息（即之前认证时存入的 JWT 字符串）</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> (String) principals.getPrimaryPrincipal();</span><br><span class="line">        <span class="type">JwtAccount</span> <span class="variable">jwtAccount</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 解析 JWT，获取负载中的信息</span></span><br><span class="line">            jwtAccount = JsonWebTokenUtil.parseJwt(jwt, JsonWebTokenUtil.SECRET_KEY);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;JWT 解析失败，无法进行授权&quot;</span>, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AuthenticationException</span>(<span class="string">&quot;Token 无效&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">SimpleAuthorizationInfo</span> <span class="variable">authorizationInfo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleAuthorizationInfo</span>();</span><br><span class="line">        <span class="comment">// 这里假设 JWT 的 payload 中包含了角色和权限信息（例如 roles: &quot;admin,user&quot;, permissions: &quot;user:read,user:write&quot;）</span></span><br><span class="line">        <span class="comment">// 实际项目中可能需要从数据库实时查询用户最新权限</span></span><br><span class="line">        <span class="comment">// authorizationInfo.addRoles(jwtAccount.getRoles());</span></span><br><span class="line">        <span class="comment">// authorizationInfo.addStringPermissions(jwtAccount.getPermissions());</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 示例：添加一个固定权限（实际应根据 jwtAccount 中的信息动态添加）</span></span><br><span class="line">        authorizationInfo.addRole(<span class="string">&quot;admin&quot;</span>);</span><br><span class="line">        authorizationInfo.addStringPermission(<span class="string">&quot;user:read&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> authorizationInfo;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 认证方法：在登录时调用，验证 token 的有效性</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> AuthenticationInfo <span class="title function_">doGetAuthenticationInfo</span><span class="params">(AuthenticationToken token)</span> <span class="keyword">throws</span> AuthenticationException &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">jwtToken</span> <span class="operator">=</span> (String) token.getCredentials();</span><br><span class="line">        <span class="keyword">if</span> (jwtToken == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AuthenticationException</span>(<span class="string">&quot;Token 不能为空&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 校验 JWT 的有效性（是否被篡改、是否过期）</span></span><br><span class="line">        JwtAccount jwtAccount;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            jwtAccount = JsonWebTokenUtil.parseJwt(jwtToken, JsonWebTokenUtil.SECRET_KEY);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;JWT 校验失败&quot;</span>, e);</span><br><span class="line">            <span class="comment">// 根据异常类型抛出不同的 AuthenticationException</span></span><br><span class="line">            <span class="keyword">if</span> (e <span class="keyword">instanceof</span> ExpiredJwtException) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AuthenticationException</span>(<span class="string">&quot;expiredJwt&quot;</span>); <span class="comment">// 这个字符串会在过滤器中捕获用于刷新 token</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AuthenticationException</span>(<span class="string">&quot;Token 无效或已过期&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 这里可以添加额外的校验，例如检查用户是否被锁定、是否被删除等（需要查询数据库）</span></span><br><span class="line">        <span class="comment">// if (!userService.isUserActive(jwtAccount.getSub())) &#123;</span></span><br><span class="line">        <span class="comment">//     throw new AuthenticationException(&quot;用户已被禁用&quot;);</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 认证成功，返回一个 AuthenticationInfo 对象，Shiro 会将其保存到 PrincipalCollection 中</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SimpleAuthenticationInfo</span>(</span><br><span class="line">                jwtToken,    <span class="comment">// Principal: 身份标识，这里我们直接使用 JWT 字符串本身</span></span><br><span class="line">                jwtToken,    <span class="comment">// Credentials: 凭证，同样为 JWT</span></span><br><span class="line">                getName()    <span class="comment">// Realm Name</span></span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-登录控制器（LoginController）"><a href="#2-登录控制器（LoginController）" class="headerlink" title="2. 登录控制器（LoginController）"></a>2. 登录控制器（<code>LoginController</code>）</h4><p>提供一个简单的登录接口，验证用户名密码后签发 JWT。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.authority.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.util.JsonWebTokenUtil;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.authority.vo.JwtAccount;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.util.AjaxResult;</span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.util.IpUtil;</span><br><span class="line"><span class="keyword">import</span> io.jsonwebtoken.SignatureAlgorithm;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.redis.core.StringRedisTemplate;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PostMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestParam;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.annotation.Resource;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> java.util.UUID;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;</span></span><br><span class="line"><span class="comment"> * 登录控制器</span></span><br><span class="line"><span class="comment"> * &lt;/p&gt;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> com.luoyx.vjsb.common.properties.VjsbProperties vjsbProperties;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户登录接口</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> AjaxResult <span class="title function_">login</span><span class="params">(<span class="meta">@RequestParam</span> String username, </span></span><br><span class="line"><span class="params">                           <span class="meta">@RequestParam</span> String password,</span></span><br><span class="line"><span class="params">                           HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 校验用户名和密码 (这里省略了具体的校验逻辑，实际应查询数据库)</span></span><br><span class="line">        <span class="comment">// User user = userService.findByUsernameAndPassword(username, password);</span></span><br><span class="line">        <span class="comment">// if (user == null) &#123;</span></span><br><span class="line">        <span class="comment">//     return AjaxResult.error(&quot;用户名或密码错误&quot;);</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 模拟一个用户ID</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">userId</span> <span class="operator">=</span> <span class="string">&quot;10001&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 生成 JWT</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> JsonWebTokenUtil.createToken(</span><br><span class="line">                UUID.randomUUID().toString(), <span class="comment">// JWT ID</span></span><br><span class="line">                userId,                      <span class="comment">// Subject (通常为用户唯一标识)</span></span><br><span class="line">                <span class="string">&quot;vjsb-server&quot;</span>,               <span class="comment">// Issuer</span></span><br><span class="line">                password,                    <span class="comment">// 可以将密码哈希后存入，但切勿明文存储敏感信息</span></span><br><span class="line">                vjsbProperties.getExpire(),  <span class="comment">// 过期时间（秒）</span></span><br><span class="line">                SignatureAlgorithm.HS512     <span class="comment">// 签名算法</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 将 JWT 存储到 Redis（用于实现 token 刷新或强制失效机制）</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;JWT-SESSION-&quot;</span> + IpUtil.getIpFromRequest(request) + <span class="string">&quot;_&quot;</span> + userId;</span><br><span class="line">        stringRedisTemplate.opsForValue().set(</span><br><span class="line">                redisKey, </span><br><span class="line">                jwt, </span><br><span class="line">                vjsbProperties.getExpire() * <span class="number">2</span>, <span class="comment">// Redis 中的过期时间稍长于 JWT 过期时间</span></span><br><span class="line">                TimeUnit.SECONDS</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. 返回 JWT 给客户端</span></span><br><span class="line">        <span class="keyword">return</span> AjaxResult.success(<span class="string">&quot;登录成功&quot;</span>, jwt);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户登出接口（可选）</span></span><br><span class="line"><span class="comment">     * 由于 JWT 是无状态的，服务器无法直接使其失效。</span></span><br><span class="line"><span class="comment">     * 常见的解决方案是在 Redis 中维护一个黑名单，或者使客户端主动丢弃 token。</span></span><br><span class="line"><span class="comment">     * 这里演示通过 Redis 黑名单实现：</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/logout&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> AjaxResult <span class="title function_">logout</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (token != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 解析 token 获取用户信息</span></span><br><span class="line">                <span class="type">JwtAccount</span> <span class="variable">jwtAccount</span> <span class="operator">=</span> JsonWebTokenUtil.parseJwt(token, JsonWebTokenUtil.SECRET_KEY);</span><br><span class="line">                <span class="type">String</span> <span class="variable">userId</span> <span class="operator">=</span> jwtAccount.getSub();</span><br><span class="line">                <span class="comment">// 将 token 加入黑名单（或直接删除 Redis 中的有效 token 记录）</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;JWT-BLACKLIST-&quot;</span> + token;</span><br><span class="line">                stringRedisTemplate.opsForValue().set(redisKey, <span class="string">&quot;logout&quot;</span>, jwtAccount.getExp() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);</span><br><span class="line">                <span class="comment">// 删除原有的 token 记录（如果存在）</span></span><br><span class="line">                stringRedisTemplate.delete(<span class="string">&quot;JWT-SESSION-&quot;</span> + IpUtil.getIpFromRequest(request) + <span class="string">&quot;_&quot;</span> + userId);</span><br><span class="line">                <span class="keyword">return</span> AjaxResult.success(<span class="string">&quot;登出成功&quot;</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;登出时解析 token 失败&quot;</span>, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(<span class="string">&quot;登出失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-全局异常处理（可选但推荐）"><a href="#3-全局异常处理（可选但推荐）" class="headerlink" title="3. 全局异常处理（可选但推荐）"></a>3. 全局异常处理（可选但推荐）</h4><p>统一处理 Shiro 抛出的认证授权异常，返回友好的 JSON 格式错误信息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.luoyx.vjsb.common.handler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.luoyx.vjsb.common.util.AjaxResult;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authc.AuthenticationException;</span><br><span class="line"><span class="keyword">import</span> org.apache.shiro.authz.AuthorizationException;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.ExceptionHandler;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestControllerAdvice;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;</span></span><br><span class="line"><span class="comment"> * 全局异常处理器</span></span><br><span class="line"><span class="comment"> * &lt;/p&gt;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> luoyuanxiang</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理认证失败异常（未登录、token无效等）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(AuthenticationException.class)</span></span><br><span class="line">    <span class="keyword">public</span> AjaxResult <span class="title function_">handleAuthenticationException</span><span class="params">(AuthenticationException e)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;认证失败: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(<span class="number">1007</span>, <span class="string">&quot;认证失败: &quot;</span> + e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理授权失败异常（无权限访问）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(AuthorizationException.class)</span></span><br><span class="line">    <span class="keyword">public</span> AjaxResult <span class="title function_">handleAuthorizationException</span><span class="params">(AuthorizationException e)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;授权失败: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(<span class="number">1008</span>, <span class="string">&quot;无权限访问&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理其他异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> AjaxResult <span class="title function_">handleException</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">        log.error(<span class="string">&quot;系统异常: &quot;</span>, e);</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(<span class="number">500</span>, <span class="string">&quot;系统内部错误&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="四、总结与使用"><a href="#四、总结与使用" class="headerlink" title="四、总结与使用"></a>四、总结与使用</h3><p>至此，一个基于 <strong>Spring Boot + Shiro + JWT + Redis</strong> 的无状态分布式认证授权系统就搭建完成了。</p><p><strong>核心流程总结：</strong></p><ol><li><strong>登录 (<code>/login</code>)</strong>：用户提供凭证，服务器校验通过后，生成 JWT 并返回给客户端，同时在 Redis 中存储一份（用于后续刷新和黑名单机制）。</li><li><strong>访问API</strong>：客户端在请求头 <code>Authorization</code> 中携带 JWT。</li><li><strong>过滤 (<code>Oauth2Filter</code>)</strong>：自定义过滤器拦截请求，提取 JWT，并调用 <code>subject.login(token)</code> 发起认证。</li><li><strong>认证 (<code>Oauth2Realm</code>)</strong>：Realm 验证 JWT 的有效性（签名、过期时间）。</li><li><strong>授权 (<code>Oauth2Realm</code>)</strong>：认证通过后，Realm 根据 JWT 中的信息（或查询数据库）为用户授予角色和权限。</li><li><strong>权限校验</strong>：Shiro 的注解（如 <code>@RequiresRoles</code>, <code>@RequiresPermissions</code>）或 URL 配置会拦截无权限的访问。</li><li><strong>登出 (<code>/logout</code>)</strong>（可选）：将 JWT 加入黑名单或删除 Redis 中的有效记录，使 token 提前失效。</li></ol><p><strong>如何使用：</strong></p><ol><li>在需要权限的控制器方法上添加 Shiro 注解：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequiresPermissions(&quot;user:read&quot;)</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/user/info&quot;)</span></span><br><span class="line"><span class="keyword">public</span> AjaxResult <span class="title function_">getUserInfo</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>前端在请求需要认证的 API 时，在 Header 中设置：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Authorization: your_jwt_token_string</span><br></pre></td></tr></table></figure></li></ol><p><strong>注意事项：</strong></p><ul><li><strong>JWT 安全性</strong>：确保使用强密钥（<code>SECRET_KEY</code>）且妥善保管。考虑定期更换密钥。</li><li><strong>敏感信息</strong>：不要在 JWT Payload 中存储敏感信息（如密码明文），因为它是可解码的。</li><li><strong>Token 失效</strong>：由于 JWT 的无状态性，实现即时失效较复杂。文中提供了基于 Redis 黑名单的示例，但会增加系统状态性。请根据业务需求权衡。</li><li><strong>性能</strong>：如果每次授权都需要查询数据库获取实时权限，可能会影响性能。可以考虑将权限信息也放入 JWT，但牺牲了实时性。</li></ul><p>项目源码已提供，可以根据实际业务需求进行进一步的调整和优化，例如集成数据库用户查询、更复杂的权限</p>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/195ce805/</id>
    <link href="https://luoyuanxiang.top/posts/195ce805/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文介绍了在分布式系统中使用Shiro进行权限控制，结合JWT替代Session解决跨域和集群认证问题，包含核心代码实现和配置示例。</summary>
    <title>Shiro与JWT整合实现分布式认证</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
  <entry>
    <author>
      <name>John Doe</name>
    </author>
    <category term="开发笔记" scheme="https://luoyuanxiang.top/categories/%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <category term="spring" scheme="https://luoyuanxiang.top/tags/spring/"/>
    <category term="spring boot" scheme="https://luoyuanxiang.top/tags/spring-boot/"/>
    <category term="aop" scheme="https://luoyuanxiang.top/tags/aop/"/>
    <category term="ioc" scheme="https://luoyuanxiang.top/tags/ioc/"/>
    <content>
      <![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Spring Framework 是一个非常流行的开源框架，为 Java 应用程序提供了广泛的支持和功能。其中，IOC 和 AOP 是 Spring Framework<br>中最重要的两个部分之一，也是 Spring Framework 能够如此受欢迎的关键所在。<br>本文将深入浅出地解读 Spring 源码中的 IOC 和 AOP 部分，包含详细的解决思路和方案、有步骤和代码、有实际的案例，旨在帮助读者更好地理解和使用<br>Spring Framework。</p><h2 id="一、IOC"><a href="#一、IOC" class="headerlink" title="一、IOC"></a>一、IOC</h2><p>什么是IOC</p><ul><li><p>IOC（Inversion of Control，控制反转）是 Spring Framework 的核心概念之一，它是一种面向对象编程的设计模式，用于实现松耦合和可重用的代码。<br>IOC 的基本思想是，将对象之间的依赖关系交由框架来管理，而不是由程序员手动编写代码来管理。</p></li><li><p>在传统的编程模型中，对象之间的依赖关系通常是硬编码在程序中的，这样会导致代码的耦合度很高，不利于代码的维护和重用。</p></li><li><p>而采用 IOC 模式可以将对象之间的依赖关系解耦，使得程序更加灵活、可扩展。</p></li></ul><h3 id="Spring-IOC-的实现原理"><a href="#Spring-IOC-的实现原理" class="headerlink" title="Spring IOC 的实现原理"></a>Spring IOC 的实现原理</h3><p>Spring IOC 的实现原理是通过容器来管理对象之间的依赖关系。<br>容器在初始化时会读取配置文件，创建并装配对象，将它们注入到其他对象中，以实现对象之间的依赖关系。<br>Spring IOC 容器可以根据配置文件中的信息来创建对象、管理对象之间的依赖关系，并为对象注入属性。</p><p>在 Spring Framework 中，IOC 的实现主要依靠两个核心接口：<code>BeanFactory</code> 和 <code>ApplicationContext</code>。</p><p><code>BeanFactory</code> 是 IOC 容器的最基本接口，提供了最简单的 IOC 容器的实现，而 <code>ApplicationContext</code> 是 <code>BeanFactory</code><br>的一个子接口，提供了更多的功能，包括 AOP、事件传递、国际化等。<br>下面我们来看一下 Spring IOC 的具体实现过程。</p><h4 id="（1）创建配置文件"><a href="#（1）创建配置文件" class="headerlink" title="（1）创建配置文件"></a>（1）创建配置文件</h4><p>首先，我们需要创建一个 XML 配置文件，用于描述对象之间的依赖关系。在配置文件中，我们需要定义 Bean 的名称、类名和属性值等信息。下面是一个简单的例子：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 用户接口 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.UserService&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;userDao&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 用户接口 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.UserDaoImpl&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;dataSource&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 数据源 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.DataSource&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;url&quot;</span> <span class="attr">value</span>=<span class="string">&quot;jdbc:mysql://localhost:3306/test&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="（2）加载配置文件"><a href="#（2）加载配置文件" class="headerlink" title="（2）加载配置文件"></a>（2）加载配置文件</h4><p>在 Spring IOC 中，容器会在启动时自动加载配置文件，创建并初始化 IOC 容器。容器会解析 XML 配置文件，根据配置文件中的信息创建相应的<br>Bean，并将它们注册到 IOC 容器中。</p><p>在 Spring 中，容器的实现类有很多种，其中最常用的是 <code>ApplicationContext</code> 和<br><code>ClassPathXmlApplicationContext</code>。<code>ClassPathXmlApplicationContext</code> 可以从类路径下加载 XML 配置文件，创建一个 IOC<br>容器，并根据配置文件中的信息创建 Bean。下面是一个简单的例子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">&quot;applicationContext.xml&quot;</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="（3）获取-Bean"><a href="#（3）获取-Bean" class="headerlink" title="（3）获取 Bean"></a>（3）获取 Bean</h3><p>在 Spring IOC 容器中，我们可以通过 Bean 的名称或类型来获取 Bean 的实例。</p><p>容器会在初始化时自动创建 Bean，并将其注册到 IOC 容器中，我们可以通过容器来获取相应的 Bean 实例。</p><p>按名称</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">UserService</span> <span class="variable">userService</span> <span class="operator">=</span> (UserService) context.getBean(<span class="string">&quot;userService&quot;</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>按类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">UserService</span> <span class="variable">userService</span> <span class="operator">=</span> context.getBean(UserService.class);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="（4）Bean-的作用域"><a href="#（4）Bean-的作用域" class="headerlink" title="（4）Bean 的作用域"></a>（4）Bean 的作用域</h3><p>在 Spring 中，Bean 的作用域指的是 Bean 实例的生命周期。<br>Spring 提供了五种常用的 Bean 作用域，分别是 <code>Singleton</code>、<code>Prototype</code>、<code>Request</code>、<code>Session</code> 和 <code>Global Session</code>。</p><p>其中，<code>Singleton</code> 是默认的 Bean 作用域，每个 Bean 在 IOC 容器中只有一个实例；<br><code>Prototype</code> 每次都会创建一个新的 Bean 实例；<code>Request</code> 和 <code>Session</code> 分别在 Web 应用程序的请求和会话范围内有效；<code>Global Session</code> 则在<br><code>Portlet</code> 环境下使用。</p><h3 id="（5）Bean-的依赖注入"><a href="#（5）Bean-的依赖注入" class="headerlink" title="（5）Bean 的依赖注入"></a>（5）Bean 的依赖注入</h3><p>Spring IOC 容器会自动管理对象之间的依赖关系，并将它们注入到对象中。在配置文件中，我们可以通过 <code>property</code> 元素来设置 Bean<br>的属性值。下面是一个简单的例子：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userService&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.UserService&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;userDao&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;userDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.UserDaoImpl&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;dataSource&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.example.DataSource&quot;</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;url&quot;</span> <span class="attr">value</span>=<span class="string">&quot;jdbc:mysql://localhost:3306/test&quot;</span>/&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在上面的配置文件中，我们定义了三个 Bean：<code>userService</code>、<code>userDao</code> 和 <code>dataSource</code>。其中，<code>userService</code> 依赖于 <code>userDao</code>，而 <code>userDao</code> 又依赖于<br><code>dataSource</code>。<br>通过配置文件，我们将 <code>dataSource</code> 注入到 <code>userDao</code> 中，将 <code>userDao</code> 注入到 <code>userService</code> 中，实现了三个 Bean 之间的依赖关系。</p><h2 id="二、AOP"><a href="#二、AOP" class="headerlink" title="二、AOP"></a>二、AOP</h2><h3 id="什么是-AOP"><a href="#什么是-AOP" class="headerlink" title="什么是 AOP"></a>什么是 AOP</h3><ul><li><p>AOP（Aspect-Oriented Programming，面向切面编程）是一种面向对象编程的设计模式，用于解决系统中的横切关注点。</p></li><li><p>AOP 的基本思想是将横切关注点（如事务管理、安全检查等）从业务逻辑中分离出来，以增强程序的模块化、可维护性和可重用性。</p></li><li><p>在 AOP 中，切面（Aspect）是一组关注点的集合，通常包括多个切点（Join Point）和增强（Advice）。</p></li><li><p>切点是程序中的特定位置，如方法调用、异常处理等。增强则是对切点执行的操作，如在方法调用前、后或抛出异常时执行某个操作。</p></li></ul><h3 id="AOP-的实现方式"><a href="#AOP-的实现方式" class="headerlink" title="AOP 的实现方式"></a>AOP 的实现方式</h3><ul><li><p>AOP 的实现方式主要有两种：基于代理（Proxy-based）和基于字节码操作（Bytecode manipulation）。</p></li><li><p>在基于代理的实现方式中，AOP 框架会在运行时动态地为目标对象生成一个代理对象，通过代理对象来织入切面逻辑。代理对象实现了目标对象所实现的接口，并将所有的方法调用转发给目标对象。在调用目标对象的方法前后，代理对象会执行相应的增强逻辑。</p></li><li><p>在基于字节码操作的实现方式中，AOP 框架会通过修改字节码来织入切面逻辑。这种方式可以在编译期或加载期对字节码进行修改，从而实现对目标对象的增强。</p></li></ul><h3 id="Spring-AOP"><a href="#Spring-AOP" class="headerlink" title="Spring AOP"></a>Spring AOP</h3><ul><li><p>Spring AOP 是 Spring 框架中的一个模块，提供了对 AOP 的支持。它基于代理的实现方式，使用 JDK 动态代理或 CGLIB 代理来生成代理对象。</p></li><li><p>在 Spring AOP 中，切面通常由增强和切点组成。增强定义了在切点执行前、后或抛出异常时要执行的操作，而切点定义了在哪些位置执行增强操作。</p></li><li><p>Spring AOP 支持四种类型的增强：前置增强、后置增强、环绕增强和异常增强。</p></li><li><p>前置增强是在目标方法执行前执行的操作，后置增强是在目标方法执行后执行的操作，环绕增强是在目标方法执行前后都执行的操作，异常增强是在目标方法抛出异常时执行的操作。</p></li></ul><h3 id="AOP-的应用场景"><a href="#AOP-的应用场景" class="headerlink" title="AOP 的应用场景"></a>AOP 的应用场景</h3><p>AOP 的应用场景包括但不限于以下几个方面：</p><ul><li><p>日志记录：在方法执行前后记录方法的输入参数和返回值等信息。</p></li><li><p>安全控制：检查用户是否有权限执行某个操作。</p></li><li><p>性能监控：在方法执行前后记录方法的执行时间，统计方法的调用次数等。</p></li><li><p>事务管理：在方法执行前后开启和关闭事务，控制事务的提交和回滚。</p></li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li><p>Spring 框架是目前最流行的企业级 Java 应用开发框架之一，它提供了一系列的特性和功能，使得开发者可以更加方便地开发高质量的企业级应用。</p></li><li><p>本文主要介绍的是 Spring AOP，它是 Spring 框架中的一个模块，提供了对 AOP 的支持。AOP<br>是一种编程范式，通过将一些与业务逻辑无关的横切关注点分离出来，从而提高了代码的可维护性和可重用性。</p></li><li><p>AOP 的实现方式主要有基于代理和基于字节码操作两种。Spring AOP 使用基于代理的实现方式，通过 JDK 动态代理或 CGLIB<br>代理生成代理对象，并在代理对象上织入切面逻辑。</p></li><li><p>在 Spring AOP 中，切面由增强和切点组成。增强定义了在切点执行前、后或抛出异常时要执行的操作，而切点定义了在哪些位置执行增强操作。Spring</p></li><li><p>AOP 支持四种类型的增强：前置增强、后置增强、环绕增强和异常增强。</p></li><li><p>AOP 的应用场景包括但不限于日志记录、安全控制、性能监控和事务管理等。在实际的开发中，我们可以使用 AOP 来实现这些功能，从而提高代码的质量和可维护性。</p></li><li><p>综上所述，Spring AOP 是 Spring 框架中非常重要的一个模块，它提供了对 AOP 的支持，使得我们可以更加方便地开发高质量的企业级应用。</p></li><li><p>掌握 Spring AOP 对于 Java 开发者来说非常重要，希望本文能够帮助大家更好地理解和应用 Spring AOP。</p></li></ul>]]>
    </content>
    <id>https://luoyuanxiang.top/posts/8a69a632/</id>
    <link href="https://luoyuanxiang.top/posts/8a69a632/"/>
    <published>2025-09-04T14:57:00.000Z</published>
    <summary>本文深入解析Spring框架核心机制IOC和AOP的实现原理，涵盖依赖注入、Bean作用域、代理机制及实际应用场景，帮助开发者深入理解Spring运作机制。</summary>
    <title>Spring IOC与AOP源码解析</title>
    <updated>2026-07-22T09:03:41.860Z</updated>
  </entry>
</feed>
