<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>黄泽彬</title>
    <description>欢迎来到我的个人站~</description>
    <link>https://zebinh.github.io/</link>
    <atom:link href="https://zebinh.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 10 Mar 2021 23:50:38 +0000</pubDate>
    <lastBuildDate>Wed, 10 Mar 2021 23:50:38 +0000</lastBuildDate>
    <generator>Jekyll v3.9.0</generator>
    
      <item>
        <title>十分钟完成的操作系统att版本</title>
        <description>&lt;h1 id=&quot;十分钟完成的操作系统att版本-一个操作系统的实现&quot;&gt;十分钟完成的操作系统at&amp;amp;t版本-《一个操作系统的实现》&lt;/h1&gt;

&lt;p&gt;以前看了《一个操作系统的实现》这本书，使用了nasm汇编和bochs虚拟机来编写一个“操作系统”。做了清华大学ucore实验后，我感觉使用at&amp;amp;t汇编和qemu虚拟机来实现一个“操作系统”更加容易调试操作系统，因此改写了成at&amp;amp;t版本。&lt;/p&gt;

&lt;p&gt;at&amp;amp;t汇编版本代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-at&amp;amp;t&quot;&gt;.code16
.section .text
.global _start 
_start:
  movw %cs, %ax
  movw %ax, %ds
  movw %ax, %ss
  call DispStr
loop1:
  jmp loop1

DispStr:
  movw $msg, %ax
  movw %ax, %bp
  movw $16, %cx
  movw $0x1301, %ax
  movw $0x000c, %bx
  movb $0x00, %dl
  int $0x10
  ret
msg:
  .ascii &quot;Hello, OS world!&quot;
  .org 510
  .word 0xAA55
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来使用as汇编、ld链接：&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;as &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; boot.o boot.s
ld &lt;span class=&quot;nt&quot;&gt;-Ttext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0x7c00 &lt;span class=&quot;nt&quot;&gt;--oformat&lt;/span&gt; binary &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; boot.bin boot.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接下来制作一个512KB的虚拟硬盘，将上面生成的“操作系统”写入第一个扇区。&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;dd &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/zero &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;boot.img &lt;span class=&quot;nv&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000
&lt;span class=&quot;nb&quot;&gt;dd &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;boot.bin &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;boot.img &lt;span class=&quot;nv&quot;&gt;conv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;notrunc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着启动qemu虚拟机，如下，就是模拟插入boot.img硬盘，不需要配置文件，比bochs更方便。&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;qemu-system-x86_64 &lt;span class=&quot;nt&quot;&gt;-hda&lt;/span&gt; boot.img &lt;span class=&quot;nt&quot;&gt;-monitor&lt;/span&gt; stdio
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;成功，如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/09/19/bX5aALNwiYrI2lx.png&quot; alt=&quot;quicker_d7118466-811e-41f7-a0e9-d2409e098768.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;qemu不用配置文件真的很方便，加上启动参数-S -s就能使用gdb单步调试了。&lt;/p&gt;
</description>
        <pubDate>Sat, 19 Sep 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/09/AttSystem/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/09/AttSystem/</guid>
        
        <category>操作系统</category>
        
        
      </item>
    
      <item>
        <title>WSL图形界面</title>
        <description>&lt;h1 id=&quot;wsl图形界面&quot;&gt;WSL图形界面&lt;/h1&gt;

&lt;p&gt;我的电脑是windows系统，以前我使用Linux都是跑在virtualbox虚拟机下。后来有了vagrant很容易就能安装Linux虚拟机。所以一直停留在virtualbox阶段。&lt;/p&gt;

&lt;p&gt;虽然知道微软Linux子系统WSL，但是一直认为它是阉割版的linux，所以没打算转移到WSL上来。最近学习动手编写操作系统，需要安装qemu虚拟机来调试操作系统，这是完全使用命令行来启动的虚拟机，运行时会弹出一个图形界面。同时vagrant和本地的windows互发文件也不方便，因此就把注意力转移到WSL上来。&lt;/p&gt;

&lt;p&gt;WSL真的是和windows配合得天衣无缝，windows下的磁盘都自动挂载到WSL下。有些事WSL不方便做的都可以由windows完成。&lt;/p&gt;

&lt;p&gt;再者装上图形界面后，随时想用命令行的linux或图形版的linux都可以。&lt;/p&gt;

&lt;h2 id=&quot;wsl安装xfce4&quot;&gt;WSL安装xfce4&lt;/h2&gt;

&lt;p&gt;WSL安装图形界面的原理是，window上安装图形界面接受服务，WSL安装发送客户端。WSL会把图形界面传送到window的服务上，就能显示图形界面了。&lt;/p&gt;

&lt;h3 id=&quot;1-更新软件源&quot;&gt;1. 更新软件源&lt;/h3&gt;

&lt;p&gt;更新软件源，强烈建议更换成阿里云的源，否则国外的源速度下到明年都下载不完。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 备份list文件&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /etc/apt
&lt;span class=&quot;nb&quot;&gt;sudo cp &lt;/span&gt;source.list source.list.bak
&lt;span class=&quot;c&quot;&gt;# 管理员权限修改source.list&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim source.list
&lt;span class=&quot;c&quot;&gt;# 然后在vim中操作替换成阿里云的源&lt;/span&gt;
:%s/security.ubuntu/mirrors.aliyun/g
:%s/archive.ubuntu/mirrors.aliyun/g
&lt;span class=&quot;c&quot;&gt;# 更新软件源&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-wsl中安装xfce4&quot;&gt;2. wsl中安装xfce4&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;xfce4
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;xfce4-session
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;安装完后，配置一下&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim ~/.bashrc
&lt;span class=&quot;c&quot;&gt;# 最后一行添加配置&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost:0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-window安装vcxsrv&quot;&gt;3. window安装VcXsrv&lt;/h3&gt;

&lt;p&gt;window上下载VcSrv并安装。安装完后启动，按如下填写。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/18/wfo0yT.png&quot; alt=&quot;wfo0yT.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后桌面会弹出一个大黑框，正常现象，不用管。&lt;/p&gt;

&lt;h3 id=&quot;4-在wsl执行命令启动图形界面&quot;&gt;4. 在WSL执行命令启动图形界面&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xfce4-session --display=localhost:0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;自此，WSL图形界面搭建结束。&lt;/p&gt;

&lt;p&gt;WSL下的terminal很难看，可以试试安装oh-my-zsh试试。&lt;/p&gt;
</description>
        <pubDate>Fri, 18 Sep 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/09/WslXfce4/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/09/WslXfce4/</guid>
        
        <category>操作系统</category>
        
        
      </item>
    
      <item>
        <title>Zookeeper源码调试环境踩坑记录</title>
        <description>&lt;h1 id=&quot;zookeeper源码调试环境踩坑记录&quot;&gt;Zookeeper源码调试环境踩坑记录&lt;/h1&gt;

&lt;h2 id=&quot;1-下载源码&quot;&gt;1. 下载源码&lt;/h2&gt;

&lt;p&gt;本文基于zookeeper源码3.6.1版本。&lt;/p&gt;

&lt;p&gt;zookeeper的源码在github上，国内下载极慢，还好国内代码托管平台gitee克隆了github上著名的开源项目，从这里下载快多了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/16/dPhZvFA1XHecOJ6.png&quot; alt=&quot;quicker_68a198a0-dda6-4866-8017-1dfe3a9a1faf.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-maven下载jar包&quot;&gt;2. maven下载jar包&lt;/h2&gt;

&lt;p&gt;项目clone到idea后，maven开始自动下载依赖的jar包。没想到速度竟然可以这么慢，索性直接改了maven的settings.xml配置，换成阿里云的maven仓库，果然速度蹭蹭蹭一下子下载完毕。&lt;/p&gt;

&lt;h2 id=&quot;3-找入口类&quot;&gt;3. 找入口类&lt;/h2&gt;

&lt;p&gt;zookeeper是一个java项目，启动时有启动脚本，执行命令./zkServer conf/zoo.cfg即可。那么我们可以从这个脚本中找到最终执行的java启动类，为QuarumPeerMain这个主类。另外一种方法可以在zookeeper启动后，执行jps -l则可以看到主类了。&lt;/p&gt;

&lt;h2 id=&quot;4-启动报错&quot;&gt;4. 启动报错&lt;/h2&gt;

&lt;p&gt;知道主类后，我们在idea中配置zookeeper的启动参数。zookeeper启动时需要一个配置文件，我们可以从源码的根目录下的conf文件中zoo_sample.cfg复制一份改名为zoo.cfg。然后启动main方法。&lt;/p&gt;

&lt;p&gt;发现报如下错误：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError: com/codahale/metrics/Reservoir
	at org.apache.zookeeper.metrics.impl.DefaultMetricsProvider$DefaultMetricsContext.lambda$getSummary$2(DefaultMetricsProvider.java:126)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705)
	at org.apache.zookeeper.metrics.impl.DefaultMetricsProvider$DefaultMetricsContext.getSummary(DefaultMetricsProvider.java:122)
	at org.apache.zookeeper.server.ServerMetrics.&amp;lt;init&amp;gt;(ServerMetrics.java:74)
	at org.apache.zookeeper.server.ServerMetrics.&amp;lt;clinit&amp;gt;(ServerMetrics.java:44)
	at org.apache.zookeeper.server.ZooKeeperServerMain.runFromConfig(ZooKeeperServerMain.java:132)
	at org.apache.zookeeper.server.ZooKeeperServerMain.initializeAndRun(ZooKeeperServerMain.java:112)
	at org.apache.zookeeper.server.ZooKeeperServerMain.main(ZooKeeperServerMain.java:67)
	at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:140)
	at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:90)
Caused by: java.lang.ClassNotFoundException: com.codahale.metrics.Reservoir
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	... 10 more

Process finished with exit code 1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;冒失缺失了一个类，导入对应的maven坐标还是无法解决。最后找到一种方法，将该main方法放到test类下面即可。如QuarumPeerMainTest。将QuarumPeerMain的main方法复制到QuarumPeerMainTest类中。再次启动即可。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/17/u9j63kpeXLz1Yvg.png&quot; alt=&quot;quicker_b2faecd6-0335-47bb-a712-dcf813428e50.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;以上，忙了大半天。&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/08/ZooKeeperDebugEviroment/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/08/ZooKeeperDebugEviroment/</guid>
        
        <category>ZooKeeper</category>
        
        
      </item>
    
      <item>
        <title>Netty源码服务器启动流程</title>
        <description>&lt;h1 id=&quot;netty源码服务器启动流程&quot;&gt;Netty源码服务器启动流程&lt;/h1&gt;

&lt;p&gt;看到这篇文章的应该都用过Netty吧。Netty服务端的模板代码如下，我们分析下它是怎么启动的。不要纠结没有关闭连接的代码，毕竟我们只是用这段代码来debug。这篇文章我主要写的是Netty服务端的启动流程。&lt;/p&gt;

&lt;p&gt;读完这篇文章你会知道：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Netty的几大组件的关系是什么？包括NioEventLoopGoup, NioEventLoop, Channle, ChannelHandler, Pipeline&lt;/li&gt;
  &lt;li&gt;Netty是怎么注册感兴趣的事件的？&lt;/li&gt;
  &lt;li&gt;Netty服务的bossGroup收到连接后，是怎么转派到workerGroup的。&lt;/li&gt;
  &lt;li&gt;Netty服务端启动时经历了什么？&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;netty.simple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.netty.bootstrap.ServerBootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.netty.channel.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.netty.channel.nio.NioEventLoopGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.netty.channel.socket.SocketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.netty.channel.socket.nio.NioServerSocketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NettyServer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;EventLoopGroup&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bossGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;NioEventLoopGroup&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workerGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;ServerBootstrap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServerBootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bossGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workerGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;NioServerSocketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ChannelOption&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SO_BACKLOG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;childOption&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ChannelOption&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SO_KEEPALIVE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;childHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ChannelInitializer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SocketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

                    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SocketChannel&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;socketChannel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;pipeline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addLast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyChatHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ChannelFuture&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sync&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6666&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;closeFuture&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;eventloopgroup事件循环组&quot;&gt;EventLoopGroup事件循环组&lt;/h2&gt;

&lt;p&gt;Netty是基于Reactor事件模型的，它将通道的连接和处理分开，bossGroup负责处理NioServerSocketChannel通道连接，通道连接后生产NioSocketChannel分派到workerGroup处理通道的读写事件和业务逻辑。具体怎么通道怎么分配的，接下来再说明。&lt;/p&gt;

&lt;p&gt;EventLoopGroup顾名思义，它就是EventLoop组，维护一堆EventLoop的，如下图(NioEventGroup实现了EventExcutor接口)。默认new EventLoopGroup()会生成(cpu核心数*2)个EventLoop，每个EventLoop绑定一个端口，所以如果服务只绑定一个端口的话，就指定为1个接口，即new EventLoopGroup(1);&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/wA6FQzfLnRovpkT.png&quot; alt=&quot;quicker_2d9ab9a7-deb0-4ef4-a230-7d549ef8ae5a.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;nioeventloop事件循环&quot;&gt;NioEventLoop事件循环&lt;/h2&gt;

&lt;p&gt;每个EventLoop都有一个死循环，负责监听Channel上的事件。看NioEventLoop的继承链，它是一个EventExecutor、EventLoop，所以NioEventLoop是一个线程池。NioEventLoop的父类SingleThreadEventExecutor维护了一个线程池对象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/hB3v4PaLSfMWTrJ.png&quot; alt=&quot;quicker_e527dc0a-cd93-444a-83a9-b2a57938435e.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;NioEventLoop的execute(Runable r)方法，这是线程次的提交方法，当提交一个线程到NioEventLoop中时，就是执行这个方法，它做的工作如下图，是将提交过来的线程task入队，然后执行NioEventLoop的线程提交方法execute，先将task入队，再看情况执行startThread()。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/svARuniWrXDp51C.png&quot; alt=&quot;quicker_a7b027ff-e7f7-453d-a8a2-bcc170288b05.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跟踪进去，它会执行doStartThread()方法。这个方法提交一个线程，立马执行了SingleThreadEventExecutor.this.run()方法。这里，虽然NioEventLoop没有继承Thread或者实现Runnable接口，不能认为它是一个线程实现类。但是它有自己的run()方法，并且初次启动NioEventLoop时会执行SingleThreadEventExecutor.this.run()方法，因此暂且可以认为它是一个线程吧。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/ZOLRkfdmPHiuMBI.png&quot; alt=&quot;quicker_db1d3441-3d5a-4923-b202-6237c5fca185.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那NioEventLoop这个名义上的线程它的run方法是什么呢，如下图，他就是一个死循环了。说到这里，就知道问什么我们说NioEventLoop是一个死循环的线程了吧。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/924bJaVnxhszULv.png&quot; alt=&quot;quicker_6f3f0e5b-fd81-458c-b9eb-42e4bb783504.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;channel通道&quot;&gt;Channel通道&lt;/h2&gt;

&lt;p&gt;再看到ChannelFuture f = b.bind(PORT).sync();这一句的bind方法，它做了初始化和注册通道的任务。即ServerBootstrap初始化了注册了通道。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/kumZJ86LDTQxi2g.png&quot; alt=&quot;quicker_95a2ed91-b18c-4556-bab1-2ef01443a210.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;他会根据.channel(NioServerSocketChannel.class)这句代码生成一个服务端的NioServerSocketChannel。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/AB5SF3knwxVGuXC.png&quot; alt=&quot;quicker_425eaf88-3f90-4126-b4e2-c2b6a09cd334.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Channel初始化时，会给Channel塞很多东西，比如感兴趣的OP_ACCEPT事件，和非阻塞的配置，看到这里就明白了Netty底层还是NIO，Netty其实就是对NIO的封装了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/ZrzDPMW7GVKcspm.png&quot; alt=&quot;quicker_a67418a8-0745-4e71-8504-9b2efb8f7d01.png&quot; /&gt;
&lt;img src=&quot;https://i.loli.net/2020/08/15/bIWaPZAjVoUBfDS.png&quot; alt=&quot;quicker_096e46c5-2e44-4ae8-8fde-eaa49fdcd93f.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还向该Channel的pipeline中加入了一个Channel初始化器ChannelInitializer，那么ChannelInitializer的initChannel什么时候执行呢？先剧透一下，这个方法在Channel注册完成后执行，现在还在Channel初始化阶段，还没执行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/LlbsyNeatpJPYgF.png&quot; alt=&quot;quicker_a31b15d8-0906-431d-91b4-b38e9065eaba.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;pipeline管道&quot;&gt;Pipeline管道&lt;/h2&gt;

&lt;p&gt;写过Netty小demo的都知道，我们需要处理自己的业务逻辑时都是向Channel对应的pipeline中加入我们的ChannelHandler。这里就讲一下围绕Channel的组件吧。&lt;/p&gt;

&lt;p&gt;如下图，每一个Channel都有自己所属的EventLoop和Pipeline。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/S6G7NxbHdVBIL5f.png&quot; alt=&quot;quicker_073d9a21-6713-41fd-a33f-f6de019ee2c7.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;pipeline维护了链表形式的ChannelHandlerContext，每次addLast()添加ChannelHandler到pipeline中时，都会被转成ChannelHandlerContext并添加一个链表节点&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/um8wyI9JDtenTCK.png&quot; alt=&quot;quicker_b7df4071-be2d-4ef0-a09a-2b3faec380a2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/6RGKyZInjXhE1Ap.png&quot; alt=&quot;quicker_598f9744-1bc2-4099-9ab0-a37ba006bf80.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;register注册&quot;&gt;register注册&lt;/h2&gt;

&lt;p&gt;Channel初始化完成之后，会执行到注册阶段，如下代码，它提交了一个任务给到EventLoop，上面的分析我们知道，提交EventLoop.execute后，会先将task入队，然后启动死循环执行task，这里是register0方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/gWciRnxfQ7UetwO.png&quot; alt=&quot;quicker_710c1d40-8664-4a9f-bd95-866c2d346924.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么register0就是异步通过EventLoop执行的了。main线程继续执行，我们断点在了NioEventLoop的线程，切换过去可以发现它启动了死循环。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/gTlKBMfL5HhDONz.png&quot; alt=&quot;quicker_b07b256d-a2a3-47c6-904b-1a908e0aff2b.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/1M7bjCvfzYZ6uhe.png&quot; alt=&quot;quicker_10eae2ca-d9c1-42cb-95fb-cec12078cfe2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;调到底层进行注册了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/VvJjShdozryQaI7.png&quot; alt=&quot;quicker_73d60f55-cdf6-45ec-a4d3-b9458ed64304.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下来看到没有，它要执行ChannelInitilizer的initChannel方法了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/L4ewQZMd7aDjh9m.png&quot; alt=&quot;quicker_bc1378d2-ea10-4911-b037-c9207bb7686d.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;它是怎么执行的呢，如下图，调用的是pipeline的execute方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/EwfbrJxaD3AnqgS.png&quot; alt=&quot;quicker_026b4da5-1ee8-47fc-82cd-017e846c58be.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跟踪进去，开始执行方法了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/dC8xrWiBqSUXJpZ.png&quot; alt=&quot;quicker_d57299d1-a4dc-4d53-92ff-531958ff392e.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跳到我们最初的initChannel方法。可以看到最后提交了一个task到EventLoop中。往pipeline中添加了一个ServerBootstrapAcceptor。bossGroup是怎么实现将连接建立后分配给workGroup的呢，这就是关键。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/XM17cbTqSDVtaQp.png&quot; alt=&quot;quicker_b52a035d-0f7b-48e9-a55a-83868de229c4.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;socketchannel连接分配&quot;&gt;SocketChannel连接分配&lt;/h2&gt;

&lt;p&gt;我们知道，如果有客户端连接到服务端的话，会依次执行pipeline中的ChannelHandler，上面我们可以看到，NioServerSocketChannle最后一个ChannelHandler就是ServerBootstrapAcceptor连接分配器。&lt;/p&gt;

&lt;p&gt;我们用客户端连接上来，发现它执行到了ServerBootstrapAcceptor的channelRead方法。并拿到一个SockerChannel，向SocketChannel添加了ChannelInitializer。然后向childGroup中注册该SocketChannel。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/ZWBJflqR9SnxArs.png&quot; alt=&quot;quicker_b302e290-7fa2-4682-afb5-e14119f2d97e.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ChildGroup会选择一个EventLoop来注册这个SocketChannel。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/15/68y2zlTajb1ptD9.png&quot; alt=&quot;quicker_83e35bb4-5a5e-45ff-94ca-4b0f5ba0171e.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;至此，Netty服务端启动流程结束。&lt;/p&gt;
</description>
        <pubDate>Sat, 15 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/08/NettyServerBootstrapStartup/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/08/NettyServerBootstrapStartup/</guid>
        
        <category>Netty</category>
        
        
      </item>
    
      <item>
        <title>轻松理解Shiro与实战</title>
        <description>&lt;h1 id=&quot;轻松理解shiro与实战&quot;&gt;轻松理解Shiro与实战&lt;/h1&gt;

&lt;p&gt;后端管理系统登录一般都涉及到权限控制，权限管理组件用的最多的就是Apache的Shiro了，任何系统的登录模块，基本都可以使用shiro来实现我们的功能。相信看到这篇文章的肯定知道了Shiro的作用，废话不多说，直入主题吧。&lt;/p&gt;

&lt;h2 id=&quot;spring-boot整合shiro&quot;&gt;Spring Boot整合Shiro&lt;/h2&gt;

&lt;p&gt;首先来思考一下正常需要权限控制的网站它的登录流程是什么样的？大概流程如下。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;设置页面拦截。系统中有些页面可以直接访问，有些页面需要登录才能访问，有些页面不仅需要登录还需要登录的用户有相关权限才能访问，这是第一步。&lt;/li&gt;
  &lt;li&gt;如果用户没登录直接访问需要登录或需要权限的页面，则调转到登录页面让用户登录&lt;/li&gt;
  &lt;li&gt;用户登录认证后，访问需要权限的页面时，系统可以识别该用户是谁并鉴权。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shiro说到底就是按上面步骤解决问题的，我们要做的仅仅是：配置拦截页和权限接口、实现认证业务逻辑、实现鉴权业务逻辑。&lt;/p&gt;

&lt;h2 id=&quot;表设计&quot;&gt;表设计&lt;/h2&gt;

&lt;p&gt;用户权限模块一般涉及到五张表，基本的三张表 + 两张关联表：(用户表base_user, 角色表base_role, 资源表base_resource) + (用户角色关联表base_user_role, 角色资源关联表base_role_resource)，以下说说表结构。&lt;/p&gt;

&lt;h3 id=&quot;用户表base_user&quot;&gt;用户表base_user&lt;/h3&gt;

&lt;p&gt;主要是用户名和密码两个字段，用来登录认证。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/10/zXiNcE1asplT38D.png&quot; alt=&quot;quicker_9ef6b341-be6d-4ce1-8e13-3f5321bef604.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;角色表&quot;&gt;角色表&lt;/h3&gt;

&lt;p&gt;系统中所有的角色&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/10/uNrLyTG3FS7Hbtj.png&quot; alt=&quot;quicker_60412b90-b8ac-46b9-bd61-69047bb44a3d.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;资源表&quot;&gt;资源表&lt;/h3&gt;

&lt;p&gt;系统中所有的资源包括菜单和按钮等，都需要权限控制。其中code代表了资源代号，其实也可以用表中的no代替用于控制权限，不过用自定义的code比较直观，比如添加文章按钮的权限可以设置为article:add&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/10/swb7BfKCFhEGOl5.png&quot; alt=&quot;quicker_e4eda3ed-26c0-43b2-9e47-7db93f3914cd.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;用户角色表&quot;&gt;用户角色表&lt;/h3&gt;

&lt;p&gt;每个用户对应多个角色，一个角色可以对应多个用户，权限资源是赋予角色的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/10/lGbhSdIOANY8n4L.png&quot; alt=&quot;quicker_4bffccd4-597c-4403-b909-b0bccf68363d.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;角色资源表&quot;&gt;角色资源表&lt;/h3&gt;

&lt;p&gt;多对多的关系。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/10/N2lmnREA98C1jyD.png&quot; alt=&quot;quicker_0c2600fe-c7aa-4ee1-bad6-6e36474d6267.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;脚本整理&quot;&gt;脚本整理&lt;/h3&gt;

&lt;p&gt;以上五个表的脚本(postgresql)&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- 用户表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bigserial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;nickname&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;base_user_primary_key&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户表'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'物理自增主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'业务主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户名'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;password&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'密码'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nickname&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'昵称'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- 角色表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bigserial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;base_role_primary_key&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'角色表'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'物理自增主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'业务主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'角色名'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- 资源表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bigserial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;parent_no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;base_resource_primary_key&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源表'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'物理自增主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'业务主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源名'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源代号，用于@RequiresPermissions'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源类型，0菜单 1按钮'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent_no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'父元素编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- 角色权限表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bigserial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;user_no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;role_no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;base_user_role_primary_key&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户角色表'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'物理自增主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'业务主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role_no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'角色编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_user_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- 角色资源表&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bigserial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;role_no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;resource_no&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int8&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;base_role_resource_primary_key&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'用户角色表'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'物理自增主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'业务主键'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role_no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'角色编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource_no&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'资源编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'创建人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改时间'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_role_resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'修改人编号'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;导入shiro-spring-boot-web-starter&quot;&gt;导入shiro-spring-boot-web-starter&lt;/h2&gt;

&lt;p&gt;maven中导入如下Shiro的坐标。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.shiro&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;shiro-spring-boot-web-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.5.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;拦截所有页面&quot;&gt;拦截所有页面&lt;/h2&gt;

&lt;p&gt;步骤都在下面代码的注释了，总结一句话就是：通过ShiroFilterFactoryBean配置拦截的页面，拦截后调转到登录页，登录过程中，SecurityManager调用Realm来获取认证、授权的信息。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.zebinh.zmall.mall.admin.config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authc.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authz.AuthorizationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authz.SimpleAuthorizationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.realm.AuthorizingRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.spring.web.ShiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.subject.PrincipalCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.web.mgt.DefaultWebSecurityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.LinkedHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * Shiro配置类
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ShiroConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;


    &lt;span class=&quot;cm&quot;&gt;/**
     *  Shiro自带的过滤器，可以在这里配置拦截页面
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ShiroFilterFactoryBean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultWebSecurityManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;securityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 1. 初始化一个ShiroFilter工程类&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ShiroFilterFactoryBean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shiroFilterFactoryBean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ShiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 2. 我们知道Shiro是通过SecurityManager来管理整个认证和授权流程的，这个SecurityManager可以在下面初始化&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setSecurityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;securityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 3. 上面我们讲过，有的页面不需登录任何人可以直接访问，有的需要登录才能访问，有的不仅要登录还需要相关权限才能访问&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LinkedHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 4. Shiro过滤器常用的有如下几种&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 4.1. anon 任何人都能访问，如登录页面&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;filterMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/user/login&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;anon&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 4.2. authc 需要登录才能访问，如系统内的全体通知页面&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;filterMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/user/notics&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;authc&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 4.3. roles需要相应的角色才能访问，当然可以改为在需要权限的接口上加注解@RequiresRoles(value = {&quot;admin&quot;,&quot;manager&quot;,&quot;writer&quot;}, logical = Logical.OR)或者@RequiresPermissions(&quot;article:add&quot;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// filterMap.put(&quot;/api/user/getUser&quot;, &quot;roles[admin]&quot;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 5. 让ShiroFilter按这个规则拦截&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setFilterChainDefinitionMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filterMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 6. 用户没登录被拦截后，当然需要调转到登录页了，这里配置登录页&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setLoginUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/user/login&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shiroFilterFactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * SecurityManager管理认证、授权整个流程
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultWebSecurityManager&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;defaultWebSecurityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserRealm&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 7. 新建一个SecurityManager供ShiroFilterFactoryBean使用&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;DefaultWebSecurityManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;securityManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultWebSecurityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 8. SecurityManager既然管理认证等信息，那他就需要一个类来帮他查数据库吧。这就是Realm类&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;securityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;securityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 自定义Realm，当SecurityBean需要来查询相关权限信息时，需要有Realm代劳
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserRealm&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;userRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserRealm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;


    &lt;span class=&quot;cm&quot;&gt;/**
     * 为了方便观看，我将UserRealm类的实现写在这里了
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserRealm&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizingRealm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 9. 前面被authc拦截后，需要认证，SecurityBean会调用这里进行认证&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthenticationInfo&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doGetAuthenticationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthenticationToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticationToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthenticationException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;执行认证逻辑&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;UsernamePasswordToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UsernamePasswordToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authenticationToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// 9.1. 为了方便演示，我这里写死了用户admin-name密码admin-pwd才能登录&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;admin-name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)){&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SimpleAuthenticationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;admin-pwd&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 10. 前面被roles拦截后，需要授权才能登录，SecurityManager需要调用这里进行权限查询&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationInfo&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doGetAuthorizationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PrincipalCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;principalCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;执行授权逻辑&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// 10.1. 为了方便演示，这里直接写死返回了admin角色，所有能登录的角色都是admin了&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;SimpleAuthorizationInfo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleAuthorizationInfo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addRole&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调转到登录页后，用户登录，直接调用Subject类的login方法即可，Subject类会调用SecurityManager进行认证。看到这里，应该就能理解本文开头的两幅图了吧。&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;io.github.zebinh.zmall.mall.admin.user.controller&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.SecurityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authc.AuthenticationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authc.UnknownAccountException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.authc.UsernamePasswordToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.shiro.subject.Subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;api/user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@PostMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;login&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@RequestBody&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Subject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSubject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;UsernamePasswordToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UsernamePasswordToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UnknownAccountException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;){&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;用户名不存在&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;用户名不存在&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthenticationException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;认证失败&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;认证失败&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;登录成功&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;getUser&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSubject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@RequiresPermissions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;article:add&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;notics&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;notics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;通知&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;前后端分离按钮显示&quot;&gt;前后端分离按钮显示&lt;/h2&gt;

&lt;p&gt;前后端分离时，后端可以提供一个查询用户权限的接口，前端如果是vue，可以使用v-if判断该用户使用有权限再显示，如：v-if=”hasPermission(‘acticle:add’)”&lt;/p&gt;

&lt;h2 id=&quot;shiro如何识别用户&quot;&gt;Shiro如何识别用户&lt;/h2&gt;

&lt;p&gt;在登录完成后，shiro会发一个cookie给客户端，包含了sessionId，因此能识别当前用户，当访问需要登录的页面时，如发现cookie中有sessionId，判断该用户已登录，则不需要调用Realm再进行认证了。cookie如下所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/03/21/8hXc5T.png&quot; alt=&quot;8hXc5T.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么问题来了，cookie只有pc端才有，移动端并没有cookie，该怎么解决移动端的shiro认证问题呢，同时这种方式无法解决单点登录的问题，应为cookie只能在同源的网站被发送到服务器。&lt;/p&gt;

&lt;p&gt;解决方案就是，这时候就要定义自己的Session管理器了。用户登录后，自定义Session管理器生成session并生成一个token给到前端，前端每次访问都传递token，shiro会调用自定义的Session管理器做校验。Session中可以放置当前登录用户的信息并放置在redis中。自定义的Session管理器每次从redis中读取数据即可。&lt;/p&gt;

&lt;h2 id=&quot;推荐阅读&quot;&gt;推荐阅读&lt;/h2&gt;

&lt;p&gt;推荐一个这个开源项目，可以理解Shiro权限的设计理念：https://github.com/Heeexy/SpringBoot-Shiro-Vue&lt;/p&gt;
</description>
        <pubDate>Tue, 11 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/08/ShiroSpringBootMybatisPlus/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/08/ShiroSpringBootMybatisPlus/</guid>
        
        <category>Shiro</category>
        
        
      </item>
    
      <item>
        <title>写给后端的前端快速搭建笔记</title>
        <description>&lt;h1 id=&quot;写给后端的前端快速搭建笔记&quot;&gt;写给后端的前端快速搭建笔记&lt;/h1&gt;

&lt;p&gt;迫于平时学习了前端之后没有形成体系，并且我是做后端的，没打前端的代码会生疏，下次又不知道怎么搭建前端框架了。因此这里记录一个前端的搭建过程，并写一个能用就行的前端界面。这里按照前端工程化的搭建流程来梳理技术栈，以Vue + ElementUI为例。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;NodeJS：NodeJS是前端工程化的重要支柱，他使得JS脱离了浏览器环境，使得JS可以像脚本一样在主机上运行。&lt;/li&gt;
  &lt;li&gt;npm：NPM是随NodeJS安装的包管理工具，类似后端的Maven，使用npm install命令可以安装当前目录下package.json需要的包，本地按照时存放在当前目录./node_modules目录，全局安装则安装nodejs的安装目录，按照NodeJS语法，可以使用require()引入NodeJs包。使用命令npm run dev可以运行package.json中scripts节点下的dev节点对应的命令，比如
    &lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;node build.js&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用npm run build就是执行命令node build.js。&lt;/p&gt;

&lt;p&gt;同时开过程中使用npm install xxx –save中save选项表示会讲这个模块写入package.json带上生产，而–save-dev则只在开发环境。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;npm下载过慢，建议使用cnpm
    &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cnpm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npm.taobao.org&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;创建项目&quot;&gt;创建项目&lt;/h2&gt;

&lt;p&gt;安装完NodeJS和npm后，就可以创建项目了。使用vue-cli脚手架来创建工程比较简单，所以先安装Vue脚手架吧。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install --global vue-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;安装完后，输入vue命令看看是否安装成功。&lt;/p&gt;

&lt;p&gt;接着使用安装完的vue脚手架，执行如下命令生成项目。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vue init webpack myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;之后，执行npm install和npm run dev，安装必要的包和启动vue项目。&lt;/p&gt;

&lt;p&gt;安装element-ui库并将其带上生产。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install element-ui --save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;开始开发&quot;&gt;开始开发&lt;/h2&gt;

&lt;h3 id=&quot;导入文件进行显示&quot;&gt;导入文件进行显示&lt;/h3&gt;

&lt;p&gt;Vue每个文件都是一个组件，文件结构为：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script&amp;gt;
&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt;
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;文件融合了html、css、js，是一个完整的组件文件。因此开始写页面时，你可以在components目录下新建一个vue文件，然后在首页导入看到效果。其实component只是一段代码，注册到vue中即可使用。前提是你将文件导入进来。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;想注册一个组件&lt;/li&gt;
  &lt;li&gt;导入组件&lt;/li&gt;
  &lt;li&gt;在template中使用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/08/09/2uMH5aAVoBr7slt.png&quot; alt=&quot;quicker_6212eaf1-25a1-4a76-a220-1d94cfba613f.png&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;Test&amp;gt;&amp;lt;/Test&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script&amp;gt;
    import Test from './components/test.vue'
    export default {
        components: {
            Test
        }
    }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;项目根目录下的src/main.js是程序入口，通用的组件可以在这里注册。比如element-ui组件。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面的通过在template中使用&lt;Test&gt;&lt;/Test&gt;引用组件，比较low了。不如用router前端路由来实现。&lt;/p&gt;

&lt;p&gt;先导出Test组件。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  export default {
    name: &quot;Test&quot;,
    data() {
      return {
        activeIndex: '1',
        activeIndex2: '1'
      };
    },
    methods: {
      handleSelect(key, keyPath) {
        console.log(key, keyPath);
      }
    }
  }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;在App.vue中放置一个路由占位符&lt;router-view&gt;&lt;/router-view&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;router-view/&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;然后在router/index.js中导入并配置它。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Test from '@/components/test.vue'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    {
      path: '/test',
      name: 'Test',
      component: Test
    }
  ]
})

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;页面布局&quot;&gt;页面布局&lt;/h3&gt;

&lt;p&gt;到了上面这一步，你就可以自定义添加自己喜欢的页面了。写前端最麻烦的就是布局，所以这里讲一下布局的使用。现在的布局主要使用flex弹性布局。&lt;/p&gt;

&lt;p&gt;首先在外层div（以下称为容器）使用display: flex，其下的子元素默认会变为行内块元素，可以定义大小。如果想定义宽度是整个屏幕占比多少，可以使用width: 33vx，视口被均分为100份，取33份。布局一般宽度用占比，高度定死。&lt;/p&gt;

&lt;p&gt;容器中有主轴和侧轴之分。默认主轴是横向的，侧轴是纵向的。子元素的排列就是通过调整主轴侧轴来实现的。&lt;/p&gt;

&lt;p&gt;如果想改变主轴的方向，可以使用flex-direction: column设置主轴为垂直方向。&lt;/p&gt;

&lt;p&gt;如果想调整主轴元素的间隔布局，可以使用justify-content&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;center：在主轴居中对齐&lt;/li&gt;
  &lt;li&gt;space-around: 平分剩余空间&lt;/li&gt;
  &lt;li&gt;sapce-between：两端对齐，再平分剩余空间&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果主轴元素过多，会导致元素被挤压在主轴上，可以使用flex-wrap: wrap让元素换行。&lt;/p&gt;

&lt;p&gt;如果想让元素垂直居中，则需要调整侧轴的布局。align-items设置侧轴布局，适用于单行。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;center：居中&lt;/li&gt;
  &lt;li&gt;stretch：拉伸，拉伸时子元素不能设置高度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当上面元素是多行的，则align-items不起作用。我们可以使用align-content来调整多行间的间距和布局。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;center: 居中&lt;/li&gt;
  &lt;li&gt;space-around: 平分剩余空间&lt;/li&gt;
  &lt;li&gt;sapce-between：两端对齐，再平分剩余空间&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面都是容器的属性，接下来介绍一些子元素的属性。&lt;/p&gt;

&lt;p&gt;如果你子元素有三个，前两个设置了宽度，想让第三个占所有剩下的宽度，可以使用flex属性。&lt;/p&gt;

&lt;p&gt;element-ui的布局建议看视频：https://www.bilibili.com/video/BV1NK4y187XH?p=5&lt;/p&gt;

&lt;p&gt;vue-router的路由视图&lt;router-view&gt;&lt;/router-view&gt;只能获取到顶层路由，即#App下的路由器，然后push进去路由地址，如果是嵌套路由，有点麻烦。&lt;/p&gt;

&lt;p&gt;前端调用后台的接口可以使用axios模块，先安装再导入。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 安装
npm install axios

# 导入并使用
import axios from 'axios'
Vue.prototype.$axios = axios
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;使用方法：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;this.$axios
      .get(&quot;https://www.v2ex.com/api/topics/hot.json&quot;)
      .then(function(res){
        console.info(res);
      })
      .catch(function(err){
        console.info(err)
      })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;上面会出现跨域报错，需要使用nginx反向代理解决。&lt;/p&gt;
</description>
        <pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/08/BigFrontEndQuickStart/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/08/BigFrontEndQuickStart/</guid>
        
        <category>Vue</category>
        
        
      </item>
    
      <item>
        <title>从RocketMQ源码了解其系统设计</title>
        <description>&lt;h1 id=&quot;从rocketmq源码了解其系统设计&quot;&gt;从RocketMQ源码了解其系统设计&lt;/h1&gt;

&lt;p&gt;文章写作时间：2020年7月30日 07点50分&lt;/p&gt;

&lt;p&gt;本篇文章RocketMQ代码基于最新的源码：rocketmq-all-4.7.1。&lt;/p&gt;

&lt;p&gt;工作中经常用到RocketMQ，只知道使用却不知道他的原理，有时候排查问题都不知从何处下手。所以最近研究了一下RocketMQ的源码，了解其系统设计，使用起来也得心应手了。&lt;/p&gt;

&lt;p&gt;读了这篇文章，你会了解到RockeMQ的架构和解决RocketMQ以下疑问。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;RocketMQ消费者消费失败后为什么会重试？&lt;/li&gt;
  &lt;li&gt;RocketMQ延时队列的原理？&lt;/li&gt;
  &lt;li&gt;RocketMQ如何将一条消息同时发送给外部的A、B系统？&lt;/li&gt;
  &lt;li&gt;内网环境如何避免其他同事的机器抢你的MQ消息？&lt;/li&gt;
  &lt;li&gt;RocketMQ消息进度如何保存？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/29/TPumAUEBZcr956I.png&quot; alt=&quot;quicker_8f6133a3-cf05-4d59-a801-f9a50848bab7.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;以上是我画的RocketMQ的系统设计图。本来想用Excel画一个，嫌画起来麻烦就用apple pencil画一个，将就着看吧，主要看我接下来文字的讲解。&lt;/p&gt;

&lt;h2 id=&quot;消息发送总流程&quot;&gt;消息发送总流程&lt;/h2&gt;

&lt;p&gt;如果只是想了解RocketQM的消息发送和消费流程的话，阅读这部分的文字就够了。下面文字讲讲上面图的流程。&lt;/p&gt;

&lt;p&gt;RocketMQ有四个概念模型，RocketMQ中间件启动时，首先启动NameServer，NameServer维护了Broker的信息，它接收Broker的心跳，Broker每隔30s会想NameServer发送心跳。NameServer并每10s扫描一下存活Broker，如果超过120s没有收到Broker的心跳，则认为该Broker宕机，将Broker的信息移除。&lt;/p&gt;

&lt;p&gt;Broker消息代理服务器，他是保存和转发消息的服务器。Broker启动时，每隔30s向NameServer发送心跳。并且接收生产者和消费者的请求。消息存储在Broker中，Broker接收到消息后都将其写入commitlog目录中，并写入到指定的队列，队列在Broker中的组织形式是consumerqueue目录下的topic名字命名的目录，每个队列以0、1、2、3数字命名。写入到commitlog后会将该消息的offset转发到consumerqueue中，消费者才能从队列中拉取消息。&lt;/p&gt;

&lt;p&gt;生产者启动时每隔30s向NameServer更新topic路由信息。生产者发送消息时，先在本地缓存中查找他要发送的topic在哪个Broker，如果找不到则请求NameServer查找。找到Topic路由和Topic下有几个队列后，生产者选择该topic下的一个队列，将消息发送到该队列。这里还涉及到故障规避算法，这里不讲解。&lt;/p&gt;

&lt;p&gt;RocketMQ的消费者获取消息的方式有推模式和拉模式。推模式其实就是客户端封装的拉模式，本质上RocketMQ只有一种拉模式。消费者会根据自身的负载均衡算法，选择本消费者需要消费的队列。请求Broker获取该队列的信息，默认32条消息。将消息拉取下来后，会将消息提交到线程池进行消费，默认10个线程。如果消费失败，会将消息发回Broker失败重试机制，并推进内存中的进度。如果成功则直接推进进度，消费者每隔10s会将消费进度发送到Broker保存。&lt;/p&gt;

&lt;p&gt;如果消费失败，Broker会将消息投递到延时队列中，所以RocketMQ的失败重试机制是基于延时队列的。RocketMQ只支持特定延时等级的延时队列，不支持自定义延时时间。每个延时队列，都有一个定时器每100ms查询该消息是否到时间了。是的话则将消息发送给对应的队列。&lt;/p&gt;

&lt;h2 id=&quot;rocketmq模型概念&quot;&gt;RocketMQ模型概念&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/MFLnhZePlRiGcX7.png&quot; alt=&quot;quicker_49b7ec23-9bcb-4483-906c-9c7f8b825c38.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，RocketMQ主要涉及4个角色：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;NameServer：命名服务器，更新和发现Broker服务&lt;/li&gt;
  &lt;li&gt;Broker：消息中转角色，负责存储、转发消息&lt;/li&gt;
  &lt;li&gt;Producer：生产者&lt;/li&gt;
  &lt;li&gt;Consumer：消费者&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;rocketmq路由中心nameserver&quot;&gt;RocketMQ路由中心NameServer&lt;/h2&gt;

&lt;p&gt;NameServer主要提供服务发现和注册功能，是一个无状态节点，可集群部署，集群间的机器不交流。&lt;/p&gt;

&lt;p&gt;源码中，NameServer启动类为NamesrvStartup，启动时它每10s扫描一个存活的broker(非存活会移除)，并启动Netty处理网络请求&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/jVYPFpd5SJfn6aO.png&quot; alt=&quot;quicker_bb057244-b60d-4b1f-a174-dd095c0e48d5.png&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;nameserver路由注册和故障剔除&quot;&gt;NameServer路由注册和故障剔除&lt;/h4&gt;

&lt;p&gt;NameServer用了4个Map来维护Topic路由信息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/cOsdfuYTo3tjNDU.png&quot; alt=&quot;quicker_d6aff96f-f69b-4d22-b7e9-1ace84725e12.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;他们的运行时结构如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/h4oAFLTRu5kewYy.png&quot; alt=&quot;quicker_431af6cd-779f-46d5-b621-0618c0b537bb.png&quot; /&gt;
&lt;img src=&quot;https://i.loli.net/2020/07/30/BzmnASscp95gYRx.png&quot; alt=&quot;quicker_0873bdbb-7fa3-4a0d-9a3c-da5faaaa1754.png&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;topicQueueTable：Topic队列表，记录每个Topic下有多少个队列，在哪个Broker上，消息消费是基于队列的。&lt;/li&gt;
  &lt;li&gt;brokerAddrTable：Broker地址表，记录Broker在哪个集群，Broker的主从地址。&lt;/li&gt;
  &lt;li&gt;clusterAddrTable：集群信息表，记录了集群下所有的Broker&lt;/li&gt;
  &lt;li&gt;brokerLiveTable：broker存活信息表，记录了broker上次心跳时间，超过120s表示broker宕机，移除该broker&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;broker心跳&quot;&gt;Broker心跳&lt;/h4&gt;

&lt;p&gt;broker每10秒发送心跳。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/pKWhdtFXfTEDqAy.png&quot; alt=&quot;quicker_ba35d4a0-ed30-4fc9-9266-997edd96e081.png&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;路由发现&quot;&gt;路由发现&lt;/h4&gt;

&lt;p&gt;RockeMQ路由发现是非实时的，当Topic路由出现变化后，NameServer不会主动推送给客户端(生产者、消费者)，而是由客户端定时拉取主题最新的路由。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/mrgNQ6FaGIfV1uk.png&quot; alt=&quot;quicker_df9e61ed-7a87-4461-b777-45a5cbef71df.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;rocketmq消息发送&quot;&gt;RocketMQ消息发送&lt;/h2&gt;

&lt;p&gt;RocketMQ支持3种消息发送方式：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;同步&lt;/li&gt;
  &lt;li&gt;异步&lt;/li&gt;
  &lt;li&gt;单向&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;主要了解的两个问题：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;消息队列如何进行负载？&lt;/li&gt;
  &lt;li&gt;消息发送如何实现高可用？&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;producer启动&quot;&gt;Producer启动&lt;/h3&gt;

&lt;p&gt;Producer启动时，会获取一个MQClientInstance客户端，用于和服务端交互。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/BrHJduhK9jTFpGE.png&quot; alt=&quot;quicker_e58374f2-bb5c-4145-93af-d0bb67e6c21d.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;同一个JVM中只有一个MQClientInstance，不管有多少生产者或者消费者。路由信息由MQClientInstance维护，所以生产者和消费者使用的是同一份路由。&lt;/p&gt;

&lt;h3 id=&quot;查找主题路由信息&quot;&gt;查找主题路由信息&lt;/h3&gt;

&lt;p&gt;本地缓存中没有Topic的路由，则实时向NameServer拉取。拉取不到，如果配置了自动创建Topic，则自动创建。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/Z7LoyhHwB9q82ps.png&quot; alt=&quot;quicker_e08b0ea3-c569-4106-9fd3-0b0116751ff5.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Topic在Broker上的存在形式是一个目录，队列MessageQueue才是存储的实体。消息的发送和接收都基于队列。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/k32zFV5sRn8GLuf.png&quot; alt=&quot;quicker_baf76ca8-2a13-4372-8482-bd7fbe1633e0.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;选择消息队列&quot;&gt;选择消息队列&lt;/h3&gt;

&lt;p&gt;选择一个队列，向这个队列发送消息。默认的负载均衡方式就是每次队列序号+1&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/KTeZhyGANrfXszR.png&quot; alt=&quot;quicker_d4ff8ba5-251d-4dfe-af56-d85dfa3e00bd.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果开启了故障规避，发送给某个Broker失败，则会规避一定的时间。所以高可用的方式就是重试与规避。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/p7oixGS6FwJeRKO.png&quot; alt=&quot;quicker_90719599-1617-4982-925a-e69fa0a935a1.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;rocketmq消息存储&quot;&gt;RocketMQ消息存储&lt;/h2&gt;

&lt;p&gt;RocketMQ消息存储主要有三个文件：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;commitlog：所有消息发送过来都会落到这个文件中&lt;/li&gt;
  &lt;li&gt;consumerqueue：这个目录下是很多以topic命名的目录，每个topic下有多个队列目录，每个队列下有多个文件。消息落到commitlog后会异步转发到这个目录中。消息的消费以这个目录为主&lt;/li&gt;
  &lt;li&gt;index：提供了以tag快速查询消息的索引文件&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;consumerqueue和index中查到的只是消息的偏移量，查到偏移量之后再到commitlog中查整个消息的信息。类似mysql的B+树索引，根据普通索引找到的是主键id，再根据主键id回表查询记录。存储文件这么设计使得写入commitlog时是顺序写，提高了效率。&lt;/p&gt;

&lt;p&gt;commitlog中每个文件1G大小，使用了系统调用mmap()内存映射文件技术。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/XhxQKVLHSCGrYTp.png&quot; alt=&quot;quicker_51d64699-a648-4453-8dd3-c7638e9a5693.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;示例图&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/5tfAhCBKHD36Gkw.png&quot; alt=&quot;quicker_eff18628-ab95-40f7-afb5-23ca26c09bc9.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;消息id(msgId)的生成方式如下，所以可以通过msgId快速从commitlog中找到消息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/zJ1E43M9p5VgcGe.png&quot; alt=&quot;quicker_1747134d-6da3-474b-a8d9-638e70446311.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;rocketmq相关文件&quot;&gt;RocketMQ相关文件&lt;/h3&gt;

&lt;p&gt;consumerOffset.json存储了集群消费模式消息消费的进度，以消费组的形式存在，如果删除了这个文件，可以设置消费组默认从commitlog的头或者尾开始重新消费。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/oXjVuq6JNZYOMl4.png&quot; alt=&quot;quicker_b38248b6-c892-4586-bf35-2e8c330a8aa0.png&quot; /&gt;
&lt;img src=&quot;https://i.loli.net/2020/07/30/nm7kAtXUij9MEZ3.png&quot; alt=&quot;quicker_579f8ea9-5d18-4e5e-a3e3-0e43941c988a.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;consumerqueue消息队列文件中存储的结构如下，最后的tagcode在消费者拉取消息时起到了过滤的作用。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/j3sUQNE6wqdSoC1.png&quot; alt=&quot;quicker_e7e3219d-750e-4fc3-baf3-60af35134207.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;消息存到了consumerqueue队列中时，如果开启了长轮询，会实时将消息返回给消费者。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/wr72uRsB5aWlx6d.png&quot; alt=&quot;quicker_a1e37488-1588-4896-b436-43fecb2f9f0a.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;过期文件定期删除&quot;&gt;过期文件定期删除&lt;/h3&gt;

&lt;p&gt;默认保留3天&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/yCfc8lILaqSNiM7.png&quot; alt=&quot;quicker_e337bdf3-532b-44a4-a822-7f1fd2c238f1.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/30/zPRrZ1qVwp3SDjJ.png&quot; alt=&quot;quicker_f2b57e53-ce74-47fd-8b53-747e8248f266.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;rocketmq消息消费&quot;&gt;RocketMQ消息消费&lt;/h2&gt;

&lt;p&gt;消息消费以组的模式开展，一个消费组内可以包含多个消费者，每一个消费组可订阅多个主题，消费组之间有集群模式与广播模式两种消费模式。集群模式，主题下的同一条消息只允许被其中一个消费者消费。广播模式，主题下的同一条消息将被集群内的所有消费者消费。消息服务器与消费者之间的消息传送也有两种方式：推模式、拉模式。所谓的拉模式，是消费端主动发起拉消息请求，而推模式是消息到达消息服务器后，推送给消息消费者。RocketMQ消息推模式的实现基于拉模式，在拉模式上包装一层，一个拉取任务完成后开始下个拉取任务。&lt;/p&gt;

&lt;h3 id=&quot;消费者启动&quot;&gt;消费者启动&lt;/h3&gt;

&lt;p&gt;消息消费者启动时会订阅指定的主题和重试主题。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/vKbTAgta1JqkLpe.png&quot; alt=&quot;quicker_569e9842-b2a5-4a1d-9549-104baf88dcc8.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;由RebalanceImpl进行负载，就是将主题下对应的队列分配给不同的消费者拉取。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/LR8y5WxJkUDKPSH.png&quot; alt=&quot;quicker_4127f482-3ba8-4108-9159-c62aad214578.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;查出所有队列，进行负载分配。有多种负载均衡算法。负载会定时执行，如果新增加了消费者则会将该消费者加入负载均衡中。如果消费者个数大于队列数，多出来的消费者将不参与消息消费。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/XpO2TvnrezQ4kZt.png&quot; alt=&quot;quicker_122a1518-7d73-4196-acb6-b34f7befc49a.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;拉取消息&quot;&gt;拉取消息&lt;/h3&gt;

&lt;p&gt;分配好负载后，每个消费者都分配到了指定的队列。开始拉取消息，并提交到线程池消费。每次默认拉取32条消息。为提高效率，消息拉取采用长轮询机制。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/vmf2ENahZdQH16b.png&quot; alt=&quot;quicker_884156c8-2cc0-423a-8d39-838ef4b508c0.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;消费模式有并发消费和顺序消费。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/KOt6Hnug4FUlkEB.png&quot; alt=&quot;quicker_366dc75a-aa5e-4951-b534-fb222a3de197.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;开始消费消息，执行业务逻辑。默认10个线程，最大20个线程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/vceCguNUSrBkhm9.png&quot; alt=&quot;quicker_0b011995-68c1-4ce3-b6c8-8ddee508b45b.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;消费失败，将消息发回Broker，失败重试。推进消费进度。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/Drq97KbQEhH8Azw.png&quot; alt=&quot;quicker_e4c61ddf-2eb4-40fd-bb66-8dcaa610aa70.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;消费失败设置delayLevel&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/I6uHdX1BUb4sRJz.png&quot; alt=&quot;quicker_0546cbea-2e6a-4f68-9a95-fdce4f728aed.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;定时消息机制&quot;&gt;定时消息机制&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/DpCQHqw6OYj5y9Z.png&quot; alt=&quot;quicker_8fcd3156-c159-4081-a796-68ad90e0b116.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;每个定时消息队列有一个定时器，定时器的任务就是遍历定时队列，将时间到了的消息封装成指定的topic写回commitlog。间隔100ms后定时器重新扫描定时队列。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/31/aM6Hzt.png&quot; alt=&quot;aM6Hzt.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;定时消息图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/xP3syKcow7YDGTq.png&quot; alt=&quot;quicker_342e4e3b-8525-4e35-8cc1-01fe99f0d5d3.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;消息重试机制&quot;&gt;消息重试机制&lt;/h2&gt;

&lt;p&gt;定时消息时通过设置delayLevel声明，消息消费失败时，也设置了delayLevel，所以消息重试机制是基于定时消息的。每次delayLevel加一，如果失败超过16次，则不会重试，投递到死信队列。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/AFV6jBgwO7D1xi3.png&quot; alt=&quot;quicker_917e1090-5f2f-4aeb-8306-8fafef52c456.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;服务端处理消息拉取&quot;&gt;服务端处理消息拉取&lt;/h2&gt;

&lt;p&gt;服务端会以tagcode先过滤一遍。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/31/2sKJPLXa4xBijpI.png&quot; alt=&quot;quicker_28b34382-8ce5-4aec-bd2f-788514cae716.png&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/08/RocketMQSourceCode/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/08/RocketMQSourceCode/</guid>
        
        <category>工具</category>
        
        
      </item>
    
      <item>
        <title>docker安装部署rocketmq和SpringBoot小实例</title>
        <description>&lt;h1 id=&quot;docker安装部署rocketmq和springboot小实例&quot;&gt;docker安装部署rocketmq和SpringBoot小实例&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/18/sRgUlu4DwYAOckQ.png&quot; alt=&quot;quicker_4a8515b7-48f3-4b2b-bce1-f442135ab7c7.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;rocketmq模型如上图所示，分为如下几个部分：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;NameServer：主要用作注册中心，用于管理Topic信息和路由信息的管理&lt;/li&gt;
  &lt;li&gt;Broker：负责存储、消息tag过滤和转发。需将自身信息上报给注册中心NameServer&lt;/li&gt;
  &lt;li&gt;Producer：生产者&lt;/li&gt;
  &lt;li&gt;Consumer：消费者&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由上各部分角色的功能可知，我们需要先安装启动NameServer，再启动Broker即可搭建完RocketMQ&lt;/p&gt;

&lt;h2 id=&quot;1-部署nameserver&quot;&gt;1. 部署NameServer&lt;/h2&gt;

&lt;p&gt;首先下载镜像：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull rocketmqinc/rocketmq:4.4.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动NameServer，暴露9876端口&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; rmqnamesrv &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 9876:9876 rocketmqinc/rocketmq:4.4.0 sh mqnamesrv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动完成后，可以curl 9876端口测试服务是否启动成功&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/18/ktyWG4up3Mw5eOb.png&quot; alt=&quot;quicker_9e183af9-2317-49a7-8bc8-70505be9aa9f.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-部署broker&quot;&gt;2. 部署Broker&lt;/h2&gt;

&lt;p&gt;RocketMQ是Java编写的程序，Broker和NameServer都在上面的镜像中，只是启动命令不同而已。&lt;/p&gt;

&lt;p&gt;启动Broker&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; rmqbroker &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 10911:10911 &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 10909:10909  &lt;span class=&quot;nt&quot;&gt;--link&lt;/span&gt; rmqnamesrv:namesrv &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;NAMESRV_ADDR=namesrv:9876&quot;&lt;/span&gt; rocketmqinc/rocketmq:4.4.0 sh mqbroker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;–link 将NameServer容器起个别名，Broker中需要配置一个NAMESRV_ADDR参数指向NameServer地址。&lt;/p&gt;

&lt;p&gt;同上，这里也可以使用curl localhost:10911验证下服务器是否启动&lt;/p&gt;

&lt;h2 id=&quot;3-部署rocketmq可视化界面控制台&quot;&gt;3. 部署RocketMQ可视化界面控制台&lt;/h2&gt;

&lt;p&gt;这一个步骤不做也可以通过Java等客户端访问到RocketMQ了，不过有可视化界面便于观察RocketMQ数据。不需要的可以跳过这一步&lt;/p&gt;

&lt;p&gt;下载镜像：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull pangliang/rocketmq-console-ng
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动容器：&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; rmqconsole &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:8080 &lt;span class=&quot;nt&quot;&gt;--link&lt;/span&gt; rmqnamesrv:namesrv &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;JAVA_OPTS=-Drocketmq.namesrv.addr=namesrv:9876&quot;&lt;/span&gt;  pangliang/rocketmq-console-ng
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;自此，也可以使用curl命令测试控制台界面是否成功启动。curl localhost:8080，如下表示启动成功。
&lt;img src=&quot;https://i.loli.net/2020/07/18/ngdqG8TOrPXhZik.png&quot; alt=&quot;quicker_c6e5c0c7-4f8d-403f-8a19-3bd517c4b54c.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;宿主机也可以登录访问控制台界面。
&lt;img src=&quot;https://i.loli.net/2020/07/18/K2CMq8561bNsOof.png&quot; alt=&quot;quicker_35196b39-6877-456c-a93b-42d3f71cd677.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-springboot整合rocketmq小实例&quot;&gt;4. SpringBoot整合RocketMQ小实例&lt;/h2&gt;

&lt;p&gt;maven中先导入apache官方提供的starter&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.rocketmq&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;rocketmq-spring-boot-starter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.1.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;application.yml配置一个name-server地址，具体值看你的机器。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/18/DgKW2x7ztpjniIQ.png&quot; alt=&quot;quicker_214effe4-c2f1-48db-838d-4915c303ef38.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里也可以通过accessKey和secureKey登录连接。默认配置在RocketMQ的配置文件中，默认值是：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;accessKey: RocketMQ
secureKey: 12345678
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;生产者发送消息：&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RocketController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RocketMQTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rocketMQTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 发送给Broker，默认会自动创建topic，topic和tag用冒号分隔&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/rocket/send&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rocketSend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;rocketMQTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;convertAndSend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rocket-topic-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 延时消息，RocketMQ支持这几个级别的延时消息，不能自定义时长&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/rocket/delayMsg/send&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rocketDelayMsgSend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;rocketMQTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;syncSend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rocket-topic-1:tag-2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessageBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withPayload&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;消费者：&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Slf4j&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RokcetServiceListener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@RocketMQMessageListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;consumerGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;consumer-group-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rocket-topic-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Consumer1&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RocketMQListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;consumer1 rocket收到消息：{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// RocketMQ支持两种消费方式，集器消费和广播消费&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@RocketMQMessageListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;consumerGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;consumer-group-2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rocket-topic-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;selectorExpression&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tag2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messageModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessageModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BROADCASTING&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Consumer2&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RocketMQListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;consumer2 rocket收到消息：{}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sat, 18 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/07/RocketMQDeploymentWithSpringBootDemo/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/07/RocketMQDeploymentWithSpringBootDemo/</guid>
        
        <category>工具</category>
        
        
      </item>
    
      <item>
        <title>优雅地使用SpringBoot注解写代码 SpringBoot常用注解</title>
        <description>&lt;h1 id=&quot;优雅的使用springboot注解写代码-springboot常用注解&quot;&gt;优雅的使用SpringBoot注解写代码 SpringBoot常用注解&lt;/h1&gt;

&lt;p&gt;本篇文章想写一下SpringBoot注解的优雅使用方法。我们所有的工程都离不开配置，那就从配置涉及到的注解开始写起吧。&lt;/p&gt;

&lt;p&gt;假设我们的项目是一个数据表结构的迁移项目，用到了两个数据源，一个MySQL的和一个PostgreSQL的。我们项目的目的就是读取MySQL的表结构，生成PostgreSQL的ddl建表语句，生成表结构。&lt;/p&gt;

&lt;p&gt;那么第一步，我们就需要配置数据源属性。&lt;/p&gt;

&lt;h2 id=&quot;1-配置文件&quot;&gt;1. 配置文件&lt;/h2&gt;
&lt;p&gt;application.yml配置文件&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;jdbc：&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;driver-class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;com.mysql.jdbc.Driver&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;this is a mysql url&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mysqlUsername&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mysqlPassword&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;postgresql&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;flase&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;driver-class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;org.postgresql.Driver&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;this is a postgresql url&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresqlUsername&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;this is a postgresql password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-configurationproperties将配置文件映射为java-bean键值对类&quot;&gt;2. @ConfigurationProperties将配置文件映射为Java Bean键值对类&lt;/h2&gt;

&lt;p&gt;写好配置文件后，Java要使用配置文件，肯定要先将配置文件封装成Java Bean。使用@ConfigurationProperties即可。如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/08/W9b8IuRxa3tGm72.png&quot; alt=&quot;quicker_a4f9f571-9299-433f-a4a1-961709d013ae.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-将properties键值对类引入configuration配置类&quot;&gt;3. 将Properties键值对类引入@Configuration配置类&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/08/TusDjPhfZ5CxXE6.png&quot; alt=&quot;quicker_e1a8ae4a-af5f-4682-be57-4727545920b4.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里的@Configuration配置类，肯定需要引入Properties键值对类。但是MysqlProperties类上的@ConfigurationProperties注解是不会往容器中注入自身bean的，所以需要@EnableConfigurationProperties启用。&lt;/p&gt;

&lt;p&gt;接着就是enabled配置的妙用了，配置@ConditionalOnProperty，当指定的键enabled为指定的值true时，该配置类才生效。&lt;/p&gt;

&lt;h2 id=&quot;4-其他注解&quot;&gt;4. 其他注解&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;@Bean：该注解往容器中注入bean，当Bean不是我们项目中的源码时，无法在其类上加@Component注解使其被容器托管，这是可以使用@Bean注解。&lt;/li&gt;
  &lt;li&gt;@Import：快速向容器中导入一个类，和@Bean类似都是导入Bean的作用，然而@Import的bean id是全类名，因此一般不会用id来获取bean。@Import配合实现了ImportSelector接口的类，可以有动态的选择的向容器中导入指定Bean。&lt;/li&gt;
  &lt;li&gt;@Profile：该注解指定了一个环境。SpringBoot中application.yml可以配置多个环境，如application-dev.yml, application-test.yml。SpringBoot应用启动时添加-Dspring.profiles.active=test指定环境，同时也会作用到@Profile注解上。&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 08 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/07/SpringBootAnnotation/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/07/SpringBootAnnotation/</guid>
        
        <category>Spring</category>
        
        
      </item>
    
      <item>
        <title>从Spring启动过程来理解IoC、AOP和bean的生命周期</title>
        <description>&lt;h1 id=&quot;从spring启动过程来理解iocaop和bean的生命周期&quot;&gt;从Spring启动过程来理解IoC、AOP和bean的生命周期&lt;/h1&gt;

&lt;p&gt;Spring的基本功能就是IoC和AOP，我们的bean都是交给Spring管理的。那么Spring IoC是怎么生成这些bean、又怎么为指定的bean进行AOP代理增强呢？答案就在Spring的启动流程中。&lt;/p&gt;

&lt;h2 id=&quot;1-spring-ioc&quot;&gt;1. Spring IoC&lt;/h2&gt;

&lt;h3 id=&quot;11-鸟瞰spring-ioc&quot;&gt;1.1. 鸟瞰Spring IoC&lt;/h3&gt;

&lt;p&gt;为了方便，这里使用注解版的写法来启动Spring IoC容器。如下图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/04/OU1Ha3ncswj8NXW.png&quot; alt=&quot;quicker_318594ba-8017-4221-89af-6ac25ef672b2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里先说总结再讲解源码，先理清脉络再深入细节才不会迷失在细节当中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/04/sBEaZjmfq91tY7c.png&quot; alt=&quot;quicker_c37fdb93-6ae9-46f0-be83-820a17c6a783.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图所示，Spring的启动过程主要可以分为两部分：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;第一步：解析成BeanDefinition：将bean定义信息解析为BeanDefinition类，不管bean信息是定义在xml中，还是通过@Bean注解标注，都能通过不同的BeanDefinitionReader转为BeanDefinition类。
    &lt;ul&gt;
      &lt;li&gt;这里分两种BeanDefinition，RootBeanDefintion和BeanDefinition。RootBeanDefinition这种是系统级别的，是启动Spring必须加载的6个Bean。BeanDefinition是我们定义的Bean。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;第二步：参照BeanDefintion定义的类信息，通过BeanFactory生成bean实例存放在缓存中。
    &lt;ul&gt;
      &lt;li&gt;这里的BeanFactoryPostProcessor是一个拦截器，在BeanDefinition实例化后，BeanFactory生成该Bean之前，可以对BeanDefinition进行修改。&lt;/li&gt;
      &lt;li&gt;BeanFactory根据BeanDefinition定义使用反射实例化Bean，实例化和初始化Bean的过程中就涉及到Bean的生命周期了，典型的问题就是Bean的循环依赖。接着，Bean实例化前会判断该Bean是否需要增强，并决定使用哪种代理来生成Bean。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bean的生命周期如下图，先有个印象即可，到源码部分再回过头来看看Bean的生命周期。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/AnMkBGIDtHbYf1u.png&quot; alt=&quot;quicker_6161358d-a49c-4cfb-ac29-14d1f89ce689.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;12-spring-ioc-源码&quot;&gt;1.2. Spring IoC 源码&lt;/h3&gt;

&lt;p&gt;我现在用的是Spring 5.2.6的源码，Spring全注解版开发。&lt;/p&gt;

&lt;p&gt;第一步就是new一个容器了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/JZOMcHlV93PWtB8.png&quot; alt=&quot;quicker_dfe02bf9-d87d-45bf-8108-1db3fa5e4a50.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进去看一下，可以看到主要有三个方法，请记牢这三个方法，this(); register(componentClasses); refresh();&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/yM1iEChdgY6zDQl.png&quot; alt=&quot;quicker_767126df-de5f-4aae-8864-ed51e35fc2bb.png&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;121-this方法&quot;&gt;1.2.1. this()方法&lt;/h4&gt;

&lt;p&gt;点击进入this，看到里层注册了6个RootBeanDefinition，即系统级别的BeanDefinition。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/Aboi6UEsTSze8Bm.png&quot; alt=&quot;quicker_c42e9e89-e1a6-4185-a6b7-c204b39eb649.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再进去，可以看到注册BeanDefinition其实就是放到BeanFactory的缓存中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/7glc9bshmriw3FP.png&quot; alt=&quot;quicker_5b940fba-094c-45d3-a684-b4d691be33ff.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;以ConfigurationClassPostProcessor类为例，其实它是一个BeanFactoryPostProcessor拦截器。注意，这部分回调的代码在refresh()中才会执行的。所以下面说的BeanFactoryPostProcessor还不会执行，而是在refresh()中执行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/l3xZWVM7iwSm61f.png&quot; alt=&quot;quicker_a74733ee-11af-438c-8377-b91177835edc.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ConfigurationClassPostProcessor他是拦截配置类并解析里面的Bean定义的。其拦截方法会检查该类是否是配置类。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/bfLMmgOTSWqDRIu.png&quot; alt=&quot;quicker_e0e266c6-2042-4d50-8f45-19ae1e9d0011.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接着解析配置类。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/bfRLyPUNBwv2391.png&quot; alt=&quot;quicker_abe3cc16-aa13-4245-b08b-716a1ed2f7bd.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;解析@Import和@Bean&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/4a2RkpolIYNQTym.png&quot; alt=&quot;quicker_86e76306-b324-46bb-b32d-2748a564686c.png&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;122-registercomponentclasses方法&quot;&gt;1.2.2. register(componentClasses)方法&lt;/h4&gt;

&lt;p&gt;这个方法主要就是来注册new AnnotationConfigApplicationContext(xxxConfiguration.class);传进来的配置类的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/Gz3lOV9ZKcpAQfy.png&quot; alt=&quot;quicker_74d24a9e-03e2-4940-b106-d94c5ee4bdb8.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;13-refresh方法&quot;&gt;1.3. refresh()方法&lt;/h3&gt;

&lt;p&gt;这是Spring启动中最重要的方法。点进去看一下。其中invokeBeanFactoryPostProcessor故名思意就是调用BeanFactory后置处理器。registerBeanPostProcessors(beanFactory)注册bean后置处理器，Bean后置处理器在Spring中应用很广泛，他能Bean创建过程中的拦截处理器，类似BeanFactoryPostProcessor也是拦截器。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/TNDCOSrydMJjsmc.png&quot; alt=&quot;quicker_0eafea75-df6b-46c2-b3dc-8cb8a63518d7.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进这个方法，finishBeanFactoryInitialization(beanFactory)。他是初始化bean的重要方法。bean既可以通过@Bean来定义，也可以通过FactoryBean来初始化。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/el2P4wCsjdUkctr.png&quot; alt=&quot;quicker_ad92e8be-1532-4e78-a362-a873e04ae2d8.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点击getBean(beanName)看看一个bean是怎么创建的，同时，这也是Bean的生命周期。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/pkIHwKbDvOxyXEf.png&quot; alt=&quot;quicker_b43cf220-352b-4988-96e1-bd3208ee3daf.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;看到createBean(beanName, mbd, args)方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/tWZBzuslTF5XPmH.png&quot; alt=&quot;quicker_36ae1662-7304-4c8b-ab0a-72489276ee37.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;bean创建过程可以分为两步，实例化Instantiation和初始化Initialization。实例化指的是创建bean实例，初始化指的是为填充bean实例属性（为属性赋值）。resolveBeforeInstantiation()方法在bean还没实例化之前执行。提供给Bean后置处理器一个返回代理的机会，当你调用被代理的bean时，实际上是执行了增强了的代理对象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/dPT1wCXFyQHbB8G.png&quot; alt=&quot;quicker_b3dbfba4-72c8-43a8-b572-6663e5a7f084.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进去doCreateBean方法。这里就是bean的生命周期了，如开篇放出的这张图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/AnMkBGIDtHbYf1u.png&quot; alt=&quot;quicker_6161358d-a49c-4cfb-ac29-14d1f89ce689.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;bean的生命周期，可以看到第一步就创建了实例&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/hW7Y6bn2EckAivQ.png&quot; alt=&quot;quicker_1c9d022c-e97d-4814-80ce-8c46368d868f.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点击该方法createBeanInstance进去，可以看到最终就是通过Java的反射来创建bean对象的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/E4Uw7RQFTV8Bkec.png&quot; alt=&quot;quicker_04d3e466-1b00-4ac9-8e28-81dcba0edde8.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进去initializeBean方法查看，可以看到和上面生命周期的图吻合。先检查Aware接口，再到Bean后置处理器的前置处理方法，接着调用初始化方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/YlZmXoVb5Q8iEFN.png&quot; alt=&quot;quicker_aca26d8a-b28c-4f6d-a92c-301857b4781f.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;bean的生命周期&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/05/NWCBPSu9gmhFpZH.png&quot; alt=&quot;quicker_5640c1c9-7c55-4093-9a30-343c7e998ad9.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;至此，bean的IoC容器功能启动流程讲解结束。&lt;/p&gt;

&lt;h3 id=&quot;spring-aop&quot;&gt;Spring AOP&lt;/h3&gt;

&lt;p&gt;Spring代理增强方面，我们在上面的IoC容器部分看到实例化bean之前，其实是有先判断bean是否需要增强，该方法为resolveBeforeInstantiation(beanName, mdbToUse)，注意该方法是在bean实例化之前的，即先判断是否需要创建代理，如果不需要才会创建bean，否则创建的是代理对象。&lt;/p&gt;

&lt;p&gt;夜深了，待续。明天再写写AOP部分的内容。&lt;/p&gt;

&lt;p&gt;我来更新啦。&lt;/p&gt;

&lt;p&gt;那么，Spring Aop是怎么创建代理的呢，我们来看下resolveBeforeInstantiation(beanName, bdbToUse)方法。&lt;/p&gt;

&lt;p&gt;可以看到它调用的是InstantiationAwareBeanPostProcessor这个Bean后置处理器的方法，从类名字上看他是拦截Bean实例化阶段、而不是初始化阶段的。点进去看一下，可以发现他是通过调用InstantiationAwareBeanPostProcessor的回调方法来生成bean对象的。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://imgchr.com/i/UiomuT&quot;&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/UiomuT.png&quot; alt=&quot;UiomuT.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;调用postProcessBeforeInstantiation方法生成对象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/Uios2t.png&quot; alt=&quot;Uios2t.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;至此，暂且打住。我们来看看我们一般是怎么使用Spring Aop的。&lt;/p&gt;

&lt;p&gt;我们会写一个@Aspect注解的切面类，并使用@EnableAspectJAutoProxy注解启用代理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/Uixh8S.png&quot; alt=&quot;Uixh8S.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进去可以发现，它导入了一个类AspectJAutoProxyRegistrar到Spring容器中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/UFP9Nn.png&quot; alt=&quot;UFP9Nn.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;该类是一个ImportBeanDefinitionRegistrar类，搜索可以发现，ImportBeanDefinitionRegistrar类会在解析配置类的时候调用registerBeanDefinitions方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/UFPfCq.png&quot; alt=&quot;UFPfCq.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;该方法会向容器中注入一个AnnotationAwareAspectJAutoProxyCreator类的Bean定义。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/UFi7SP.png&quot; alt=&quot;UFi7SP.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;AnnotationAwareAspectJAutoProxyCreator类是一个InstantiationAwareBeanPostProcessor。&lt;/p&gt;

&lt;p&gt;讲到这里，就和上面吻合了，实例化bean时现执行InstantiationAwareBeanPostProcessor，如果有返回对象，则使用该对象，否则才去创建实例。所以@EnableAspectJAutoProxy注解的作用就是向容器中添加一个InstantiationAwareBeanPostProcessor类，拦截bean的创建并生成代理对象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/06/sXx7a9z3fylWpPj.png&quot; alt=&quot;quicker_183097d7-606d-4d14-a9d5-896a174ed0d0.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;该拦截处理器如下，创建了一个代理对象并返回。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2020/07/06/CWbjhXkMxSHTZP5.png&quot; alt=&quot;quicker_6adc4364-70bd-4133-bcd9-133c1bf3bf95.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点进来createProxy(beanClass, beanName, specificInterceptor, targetSource)方法并跟踪下层代码，可以发现动态代理创建有两种方式，如果该类是接口，则使用JDK动态代理，否则使用的是Cglib代理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/07/06/UFAprT.png&quot; alt=&quot;UFAprT.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;至此，Aop部分代码讲解结束。&lt;/p&gt;

&lt;p&gt;其中，IoC的循环依赖问题和Aop的拦截器链设计有兴趣的可以推荐看一下源码。&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://zebinh.github.io/2020/07/SpringStartupAndIoCAndAop/</link>
        <guid isPermaLink="true">https://zebinh.github.io/2020/07/SpringStartupAndIoCAndAop/</guid>
        
        <category>Spring</category>
        
        
      </item>
    
  </channel>
</rss>
