ai-course/PlayEdu后端开发手册_新版/2EhiUCoNqHwf8MP4FqLMag/PlayEdu后端开发手册_新版.html
2025-11-18 13:32:46 +08:00

335 lines
111 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-Hans-CN"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=Edge"/><link rel="stylesheet" type="text/css" href="css/modern-norm.min.css"/><link rel="stylesheet" type="text/css" href="css/prism.min.css"/><link rel="stylesheet" type="text/css" href="css/katex.min.css"/><link rel="stylesheet" type="text/css" href="css/wolai.css"/><title>PlayEdu后端开发手册_新版 - wolai 笔记</title><link rel="shortcut icon" href="data:image/svg+xml,%3Csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 800 800&apos;%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:%23fff;%7D%3C/style%3E%3C/defs%3E%3Cg%3E%3Cpath class=&apos;cls-1&apos; d=&apos;M610.08,0c66,0,90,6.88,114.13,19.79a134.62,134.62,0,0,1,56,56l2.28,4.4C793.93,103,800,127.88,800,189.92V610.08l-.08,11.56c-.78,57.38-7.58,79.89-19.71,102.57a134.62,134.62,0,0,1-56,56l-4.4,2.28C697,793.93,672.12,800,610.08,800H189.92l-11.56-.08c-57.38-.78-79.89-7.58-102.57-19.71a134.62,134.62,0,0,1-56-56l-2.28-4.4C6.44,697.75.4,673.72,0,616L0,189.92c0-66,6.88-90,19.79-114.13a134.62,134.62,0,0,1,56-56l4.4-2.28C102.25,6.44,126.28.4,184,0Z&apos;/%3E%3Cpath d=&apos;M610.08,0c66,0,90,6.88,114.13,19.79a134.62,134.62,0,0,1,56,56l2.28,4.4C793.93,103,800,127.88,800,189.92V610.08l-.08,11.56c-.78,57.38-7.58,79.89-19.71,102.57a134.62,134.62,0,0,1-56,56l-4.4,2.28C697,793.93,672.12,800,610.08,800H189.92l-11.56-.08c-57.38-.78-79.89-7.58-102.57-19.71a134.62,134.62,0,0,1-56-56l-2.28-4.4C6.44,697.75.4,673.72,0,616L0,189.92c0-66,6.88-90,19.79-114.13a134.62,134.62,0,0,1,56-56l4.4-2.28C102.25,6.44,126.28.4,184,0Zm4.72,88.9H185.2L172.42,89c-32.78.62-43.68,3.24-54.71,9.14a45.84,45.84,0,0,0-19.54,19.54c-6.61,12.36-9.11,24.55-9.27,67.49V614.8L89,627.58c.62,32.78,3.24,43.68,9.14,54.71a45.84,45.84,0,0,0,19.54,19.54c12.36,6.61,24.55,9.11,67.49,9.27H610.08c46.79,0,59.41-2.44,72.21-9.28a45.84,45.84,0,0,0,19.54-19.54c6.61-12.36,9.11-24.55,9.27-67.49V189.92c0-46.79-2.44-59.41-9.28-72.21a45.84,45.84,0,0,0-19.54-19.54C669.93,91.56,657.74,89.06,614.8,88.9ZM233.33,493.33A73.34,73.34,0,1,1,160,566.67,73.35,73.35,0,0,1,233.33,493.33Z&apos;/%3E%3C/g%3E%3C/svg%3E"></link></head><body><header><div class="image"></div><div class="title"><div class="banner"><div data-symbol="🌮" class="icon"></div></div><div data-title="PlayEdu后端开发手册_新版" class="main-title"></div></div></header><article><h2 id="49ZnrnZBxeJtyPzF4kyeKp" class="wolai-block"><span class="inline-wrap">一、背景</span></h2><div id="jBXTLvzyQQZSMtbKXfe4xd" class="wolai-block wolai-text"><div><span class="inline-wrap">Playedu<span class="jill"></span>的后端服务是一个基于 Java17 + SpringBoot3 开发的多模块<span class="jill"></span>API<span class="jill"></span>程序。</span></div></div><h2 id="jKhr9o7oZvQ9QgGJkYwKYT" class="wolai-block"><span class="inline-wrap">二、开发运行快速上手</span></h2><h3 id="b17MD2hJRiAKRvoGfZic7n" class="wolai-block"><span class="inline-wrap"><b>2.1</b></span><span class="inline-wrap">环境要求</span></h3><ul class="wolai-block"><li id="raENNN1F7ZgZaZnEDhhczR"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">操作系统Window,Mac,Linux<span class="jill"></span>均可以</span></li><li id="64iRLyy8pwSkC5k1uNPMSS"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">Openjdk ≥ 17</span></li><li id="gvnnay7udwEDJeWmeXsAM2"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">Maven ≥ 3.0</span></li><li id="qy7uz21WU9qR9FcULLxyko"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">MySQL ≥ 5.6</span></li><li id="7YRYrhcrjEcDvRjpN2i8kh"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">Redis ≥ 7.0</span></li><li id="nCNSANrmR2Kt7KeNxjnZdt"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">Kafka ≥ 1.0</span></li><li id="45wo9QF1okMKLyBKjsmo45"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">开发工具IDEA</span></li></ul><h3 id="m8meV5hrRxYrgVHTqDn37r" class="wolai-block"><span class="inline-wrap">2.2 运行系统</span></h3><h4 id="8eNCBzi7YwD8qGXHUjkL8j" class="wolai-block"><span class="inline-wrap">2.2.1<span class="jill"></span>配置文件</span></h4><ul class="wolai-block"><li id="iTA7hNP9wQ8MnvV1BaJcCg"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">编辑</span><span class="inline-wrap"><code>playedu-api</code></span><span class="inline-wrap">模块中</span><span class="inline-wrap"><code>resources</code></span><span class="inline-wrap">目录下的</span><span class="inline-wrap"><code>application.yml</code></span></li></ul><code-block id="v3xMN59VGbctJQ7pNG9sxz" class="wolai-block"><div class="wolai-pre"><div data-lang="YAML" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token key atrule">spring</span><span class="token punctuation">:</span>
<span class="token comment"># 修改数据库连接</span>
<span class="token key atrule">datasource</span><span class="token punctuation">:</span>
<span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver
<span class="token comment"># 数据库地址</span>
<span class="token key atrule">url</span><span class="token punctuation">:</span> <span class="token string">"jdbc:mysql://localhost:3306/playedu?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true&amp;allowPublicKeyRetrieval=true&amp;useSSL=false"</span>
<span class="token comment"># 数据库账号</span>
<span class="token key atrule">username</span><span class="token punctuation">:</span> <span class="token string">"root"</span>
<span class="token comment"># 数据库密码</span>
<span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token string">"123456"</span>
<span class="token comment"># 修改redis连接</span>
<span class="token key atrule">data</span><span class="token punctuation">:</span>
<span class="token key atrule">redis</span><span class="token punctuation">:</span>
<span class="token comment"># redis地址</span>
<span class="token key atrule">host</span><span class="token punctuation">:</span> <span class="token string">"127.0.0.1"</span>
<span class="token comment"># redis端口</span>
<span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>
<span class="token comment"># redis密码</span>
<span class="token key atrule">password</span><span class="token punctuation">:</span>
<span class="token comment"># Kafka</span>
<span class="token key atrule">kafka</span><span class="token punctuation">:</span>
<span class="token key atrule">bootstrap-servers</span><span class="token punctuation">:</span> <span class="token string">"kafka的连接地址"</span></pre></div></code-block><div id="j3Lf1zST8Nh5pihSLtDhBX" class="wolai-block wolai-text"><div><span class="inline-wrap">您需要修改上述文件中的 </span><span class="inline-wrap"><code>mysql</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>redis</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>kafka</code></span><span class="inline-wrap">配置。</span></div></div><h4 id="3zQQSfQj7q8v2WNbGiMG2M" class="wolai-block"><span class="inline-wrap">2.2.2 程序跑起来</span></h4><div id="jVQUmX2Gj8cJwBV2ACCrrc" class="wolai-block wolai-text"><div><span class="inline-wrap">运行启动类</span><span class="inline-wrap"><code>xyz.playedu.api.PlayeduApiApplication</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>main</code></span><span class="inline-wrap">函数</span></div></div><div id="xBcQuZUxeUiFbhUAmMkpE4" class="wolai-block"><figure class="wolai-center" style="width: 100%; flex-direction: column"><img src="media/image.png" style="width: 100%"/></figure></div><div id="fYZLkt7A3EzX7LzkXt5q42" class="wolai-block wolai-text"><div><span class="inline-wrap">跑起来之后可以看到这个表示启动成功:</span></div></div><div id="ezL6m9BHZFRQ6gkYMSLfZe" class="wolai-block"><figure class="wolai-center" style="width: 100%; flex-direction: column"><img src="media/image_1.png" style="width: 100%"/></figure></div><div id="o8KNhwFaYn5jfJumAVPDJ1" class="wolai-block wolai-text"><div><span class="inline-wrap">也可以通过再浏览器输入</span><span class="inline-wrap"><a href="http://127.0.0.1:9898"><span>http://127.0.0.1:9898</span></a></span><span class="inline-wrap">测试一下</span></div></div><div id="gXcgmGEgwCa8E8LSBgiYJJ" class="wolai-block"><figure class="wolai-center" style="width: 455.2px; flex-direction: column"><img src="media/image_2.png" style="width: 100%"/></figure></div><h2 id="hYUssERjwH7nMVAdySNRQS" class="wolai-block"><span class="inline-wrap">三、生产部署快速上手</span></h2><div id="53fDspLzEKLk6wSb1Dxk3S" class="wolai-block wolai-text"><div><span class="inline-wrap">当我们程序二开完成之后,需要将程序部署更新到生产服务器,在部署之前,我们需要编译程序。下面将是编译的流程。</span></div></div><h4 id="wGL2rfKykGzf6NnN6bma2z" class="wolai-block"><span class="inline-wrap">3.1 生产环境配置文件</span></h4><div id="mtCBXirGcyn32tfVLUwhTo" class="wolai-block wolai-text"><div><span class="inline-wrap">编辑</span><span class="inline-wrap"><code>playedu-api</code></span><span class="inline-wrap">模块中</span><span class="inline-wrap"><code>resources</code></span><span class="inline-wrap">目录下的</span><span class="inline-wrap"><code>application.yml</code></span></div></div><code-block id="dQstMcuzccuS4JxXPKrrQS" class="wolai-block"><div class="wolai-pre"><div data-lang="YAML" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token key atrule">spring</span><span class="token punctuation">:</span>
<span class="token comment"># 修改数据库连接</span>
<span class="token key atrule">datasource</span><span class="token punctuation">:</span>
<span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver
<span class="token comment"># 数据库地址</span>
<span class="token key atrule">url</span><span class="token punctuation">:</span> <span class="token string">"jdbc:mysql://localhost:3306/playedu?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true&amp;allowPublicKeyRetrieval=true&amp;useSSL=false"</span>
<span class="token comment"># 数据库账号</span>
<span class="token key atrule">username</span><span class="token punctuation">:</span> <span class="token string">"root"</span>
<span class="token comment"># 数据库密码</span>
<span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token string">"123456"</span>
<span class="token comment"># 修改redis连接</span>
<span class="token key atrule">data</span><span class="token punctuation">:</span>
<span class="token key atrule">redis</span><span class="token punctuation">:</span>
<span class="token comment"># redis地址</span>
<span class="token key atrule">host</span><span class="token punctuation">:</span> <span class="token string">"127.0.0.1"</span>
<span class="token comment"># redis端口</span>
<span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>
<span class="token comment"># redis密码</span>
<span class="token key atrule">password</span><span class="token punctuation">:</span>
<span class="token comment"># Kafka</span>
<span class="token key atrule">kafka</span><span class="token punctuation">:</span>
<span class="token key atrule">bootstrap-servers</span><span class="token punctuation">:</span> <span class="token string">"kafka的连接地址"</span></pre></div></code-block><div id="1BcUwQSBggSxwBHKsKgA7B" class="wolai-block wolai-text"><div><span class="inline-wrap">您需要修改上述文件中的 </span><span class="inline-wrap"><code>mysql</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>redis</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>kafka</code></span><span class="inline-wrap">配置,将它们修改成为您的线上生产环境配置。</span></div></div><h4 id="xtckPpcAFR5JBhNc73hCNm" class="wolai-block"><span class="inline-wrap">3.2 编译程序</span></h4><div id="xnqwKw2Ukn1E99PKyYjVfu" class="wolai-block wolai-text"><div><span class="inline-wrap">在项目根目录执行下面打包命令:</span></div></div><code-block id="xAMypNextXaS4vV4ZoMuod" class="wolai-block"><div class="wolai-pre"><div data-lang="纯文本" class="marker"></div><pre><span class="token punctuation">.</span><span class="token operator">/</span>mvnw clean <span class="token keyword">package</span> </pre></div></code-block><aside id="mMHwMw3qJGbemg7nZ2DHeB" class="bg-cultured wolai-block"><div data-symbol="📌" class="icon"></div><span class="inline-wrap">提示 项目编译</span><span class="inline-wrap">生成</span><span class="inline-wrap"><span class="jill"></span>jar<span class="jill"></span>包会在</span><span class="inline-wrap"><code>playedu-api</code></span><span class="inline-wrap">模块下</span><span class="inline-wrap"><code>target</code></span><span class="inline-wrap">文件夹</span></aside><div id="uizT5Cjz2Ds3qJCpnSwoiX" class="wolai-block"><figure class="wolai-center" style="width: 284.8px; flex-direction: column"><img src="media/image_3.png" style="width: 100%"/></figure></div><div id="owhDErw52Ux8pPCd4g6LAE" class="wolai-block wolai-text"><div><span class="inline-wrap">这就是编译完成了。编译生成的<span class="jill"></span>jar<span class="jill"></span>包会在</span><span class="inline-wrap"><code>playedu-api</code></span><span class="inline-wrap">模块下</span><span class="inline-wrap"><code>target</code></span><span class="inline-wrap">文件夹。到这里,我们就可以将 playedu-api-xxx.jar 文件部署到生产环境服务器了。</span></div></div><h4 id="7FAqeyAWzLNr9ccLTSANJE" class="wolai-block"><span class="inline-wrap">3.3 项目启动</span></h4><div id="gwbiL3KRPDZzTMwbGTnndW" class="wolai-block wolai-text"><div><span class="inline-wrap">使用命令行执行:</span></div></div><code-block id="9pVUGAw3XLy1v4oFKqECim" class="wolai-block"><div class="wolai-pre"><div data-lang="Bash" class="marker"></div><pre><span class="token function">java</span> jar playedu-api-xxx.jar</pre></div></code-block><h2 id="6mmxXgR1LqDQgMTMDrpz9A" class="wolai-block"><span class="inline-wrap">四、项目介绍</span></h2><h4 id="rdtUnydMt5sA88zdDZWjh5" class="wolai-block"><span class="inline-wrap">4.1<span class="jill"></span>文件目录结构</span></h4><code-block id="5M9Y5o1DX7J3TpjNWu3THr" class="wolai-block"><div class="wolai-pre"><div data-lang="Text" class="marker"></div><pre>playedu
├── playedu-api // 接口模块
│ └── controller // controller层
│ └── event // 事件发布器
│ └── listener // 事件监听器
│ └── interceptor // 拦截器
│ └── request // 自定义请求实体
│ └── task // 定时任务
├── playedu-common // 公共模块
│ └── annotation // 自定义注解
│ └── bus // 全局权限数据
│ └── caches // 全局缓存
│ └── config // 全局配置
│ └── constant // 通用常量
│ └── context // 用户全局数据
│ └── exception // 通用异常
│ └── type // 自定义实体
│ └── utils // 工具类
│ └── domain // 数据实体层
│ └── mapper // mapper层
│ └── service // service层
│ ├── resource // 静态资源
│ └── mapper // sql映射文件
│ └── i18n // 多语言
│ └── ipdb // IP地址资源文件
│ └── lua // lua文件
├── playedu-course // 线上课模块
├── playedu-exam // 考试模块
├── playedu-resource // 资源模块
├── playedu-system // 系统模块
│ └── aspectj // 切面代码
│ └── checks // 内置数据</pre></div></code-block><h3 id="gQ9NptBKPYM8apaVVquxP5" class="wolai-block"><span class="inline-wrap">4.2<span class="jill"></span>核心概念</span></h3><h4 id="3p6JuecHeHDJL69PUuvnZ5" class="wolai-block"><span class="inline-wrap">4.2.1 消息队列</span><div id="dw5QVDYobgBjHHvD9hx2Ly" class="wolai-block wolai-text"><div><span class="inline-wrap">PlayEdu<span class="jill"></span>引入消息队列<span class="jill"></span>kafka,用于业务的解耦</span></div></div><ul class="wolai-block"><li id="w7g7GcjfVRw4u6ahyjLnBA"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">通过<span class="jill"></span>avro<span class="jill"></span>插件初始化传参实体</span><ol class="wolai-block"><li id="suinZE17YpgtfEgrSz5xcE"><div class="marker"></div><span class="inline-wrap">定义<span class="jill"></span>schema<span class="jill"></span>文件,需要在<span class="jill"></span>playedu-api<span class="jill"></span>模块/src/main/avro<span class="jill"></span>目录里新增</span><div id="pDCGpQzaJwm22kkTVofm51" class="wolai-block wolai-text"><div><span class="inline-wrap">// 定义消息传递的参数</span></div></div><code-block id="nnhDHZMUDgFwkWaPdUkhjD" class="wolai-block"><div class="wolai-pre"><div data-lang="JSON" class="marker"></div><pre><span class="token punctuation">{</span>
<span class="token comment">// 实体文件输出路径</span>
<span class="token property">"namespace"</span><span class="token operator">:</span> <span class="token string">"xyz.playedu.api.avro.course"</span><span class="token punctuation">,</span>
<span class="token comment">// 类型record</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"record"</span><span class="token punctuation">,</span>
<span class="token comment">// 实体文件名称</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"CourseAvro"</span><span class="token punctuation">,</span>
<span class="token property">"fields"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
<span class="token comment">// 参数字段及类型</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"id"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"adminId"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"int"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"createdAt"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"courseId"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"int"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"depIds"</span><span class="token punctuation">,</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"array"</span><span class="token punctuation">,</span><span class="token property">"items"</span><span class="token operator">:</span> <span class="token string">"int"</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"userIds"</span><span class="token punctuation">,</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"array"</span><span class="token punctuation">,</span><span class="token property">"items"</span><span class="token operator">:</span> <span class="token string">"int"</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"groupIds"</span><span class="token punctuation">,</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"array"</span><span class="token punctuation">,</span><span class="token property">"items"</span><span class="token operator">:</span> <span class="token string">"int"</span><span class="token punctuation">}</span><span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span></pre></div></code-block></li></ol><div id="vJp2dxwJKYfnefUeutjB5n" class="wolai-block wolai-text"><div><span class="inline-wrap"> 2. 运行<span class="jill"></span>mvn avro:schema</span></div><div id="dv6bWbkBLfbVmTr5tSJ5gm" class="wolai-block"><figure class="wolai-left" style="width: 477px; flex-direction: column"><img src="media/image_4.png" style="width: 100%"/></figure></div><div id="tcwKGTPmJispH8bSQrzD6m" class="wolai-block wolai-text"><div><span class="inline-wrap">生成的<span class="jill"></span>java<span class="jill"></span>类在项目文件在/target/generated-sources/avro/目录里,并且复制到需要用到的实体目录中</span></div></div><div id="bXXEMqB1JP6gZqLLDvozcG" class="wolai-block"><figure class="wolai-left" style="width: 522px; flex-direction: column"><img src="media/image_5.png" style="width: 100%"/></figure></div></div><div id="owUeXRgC7PvDmNX1i7YhL" class="wolai-block wolai-text"><div><span class="inline-wrap">注意avro<span class="jill"></span>插件生成实体文件禁止直接修改</span></div></div><div id="xA2ZRC3iXEmHJiPmvs74FF" class="wolai-block wolai-text"><div><span class="inline-wrap">如果需要修改,需要修改<span class="jill"></span>schema<span class="jill"></span>文件,重新生成实体文件,并复制覆盖原文件</span></div></div></li><li id="fyCdZLQry2eGJG9SdJcZYW"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">定义一个生产者</span><code-block id="5U89WbBYp5jmjnbUR9UNqT" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> <span class="token class-name">KafkaTemplate</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> kafkaTemplate<span class="token punctuation">;</span>
<span class="token comment">// 消息传递的参数 CreatedAvro替换成新增的实体对象</span>
<span class="token class-name">CreatedAvro</span> avro <span class="token operator">=</span>
<span class="token class-name">CreatedAvro</span><span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token class-name">HelperUtil</span><span class="token punctuation">.</span><span class="token function">randomString</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment">// 必须入参消息随机id</span>
<span class="token punctuation">.</span><span class="token function">setAdminId</span><span class="token punctuation">(</span><span class="token class-name">BCtx</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">setCreatedAt</span><span class="token punctuation">(</span><span class="token class-name">DateUtil</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 消息发送到topic_test</span>
kafkaTemplate<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>
<span class="token string">"topic_test"</span><span class="token punctuation">,</span>
<span class="token class-name">CreatedAvro</span><span class="token punctuation">.</span><span class="token function">getEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>avro<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</pre></div></code-block></li><li id="ukzRdvMRn1GCMkrcabnCkV"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">定义一个消费者,实现批量消费、消费失败重试等功能</span><code-block id="cJyZq5Jom6JQon3Rsp4uNt" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> <span class="token class-name">KafkaConsumerRetryHandleV2</span> kafkaConsumerRetryHandleV2<span class="token punctuation">;</span>
<span class="token comment">// 监听消费topic_test</span>
<span class="token annotation punctuation">@KafkaListener</span><span class="token punctuation">(</span>topics <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"topic_test"</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">doKafkaConsumer</span><span class="token punctuation">(</span>
<span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token class-name">ConsumerRecord</span><span class="token operator">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">>></span> recordList<span class="token punctuation">,</span> <span class="token class-name">Acknowledgment</span> ack<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtil</span><span class="token punctuation">.</span><span class="token function">isNotEmpty</span><span class="token punctuation">(</span>recordList<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">ConsumerRecord</span><span class="token operator">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span> consumerRecord <span class="token operator">:</span> recordList<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtil</span><span class="token punctuation">.</span><span class="token function">isNotNull</span><span class="token punctuation">(</span>consumerRecord<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token comment">// 解析参数</span>
<span class="token class-name">CreatedAvro</span> avro <span class="token operator">=</span> <span class="token class-name">CreatedAvro</span><span class="token punctuation">.</span><span class="token function">getDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>consumerRecord<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Integer</span> adminId <span class="token operator">=</span> avro<span class="token punctuation">.</span><span class="token function">getAdminId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// !!!!!!!!执行业务开始!!!!!!</span>
<span class="token constant">XXXXXXXXX</span>
<span class="token comment">// !!!!!!!!执行业务结束!!!!!!</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">StringUtil</span><span class="token punctuation">.</span><span class="token function">isNotEmpty</span><span class="token punctuation">(</span>avro<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 重试成功</span>
kafkaConsumerRetryHandleV2<span class="token punctuation">.</span><span class="token function">doUpdateSuccess</span><span class="token punctuation">(</span>avro<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 执行失败需要记录到重试表,等待定时任务重试</span>
<span class="token class-name">String</span> topic <span class="token operator">=</span> consumerRecord<span class="token punctuation">.</span><span class="token function">topic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
kafkaConsumerRetryHandleV2<span class="token punctuation">.</span><span class="token function">doRetryHandle</span><span class="token punctuation">(</span>
topic<span class="token punctuation">,</span> consumerRecord<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// 手动批量提交offset</span>
ack<span class="token punctuation">.</span><span class="token function">acknowledge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</pre></div></code-block><div id="oFmPeQkC14kUSMutKwRUGm" class="wolai-block wolai-text"><div><span class="inline-wrap"></span><br/></div></div></li><li id="usjPTNi15SvXNSieJsvWCk"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">确认消费失败的消息,再次重新消费步骤</span><div id="hzTahB5K4DLNZVHTMLk748" class="wolai-block wolai-text"><div><span class="inline-wrap">查询<span class="jill"></span>kafka_consumer_retry_log<span class="jill"></span><span class="jill"></span>state=2(重试状态[0:等待重试,1:重试成功,2:重试失败])</span></div></div><div id="aKNz6tLhndYw5yaMhQXwRY" class="wolai-block wolai-text"><div><span class="inline-wrap">的消息,将<span class="jill"></span>state<span class="jill"></span>设置为<span class="jill"></span>0<span class="jill"></span>即可再次重新消费</span></div></div><div id="kNJJqYzjTpBtEvBdoM6pYb" class="wolai-block wolai-text"><div><span class="inline-wrap"></span><br/></div></div></li><li id="xyhgzDaavdwo4QGNG1mKZg"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">PlayEdu<span class="jill"></span>项目现有的<span class="jill"></span>topic<span class="jill"></span>对应消费者以及触发时机</span><div id="nkyF5daWnGVc5pA3DoPUZB" class="wolai-block wolai-simple-table"><table><tbody><tr><td style="width: 81px"><span class="inline-wrap">功能</span></td><td style="width: 168px"><span class="inline-wrap">topic</span></td><td style="width: 215px"><span class="inline-wrap">消费者</span></td><td style="width: 227px"><span class="inline-wrap">触发时机</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap">登录</span></td><td style="width: 168px"><span class="inline-wrap">topic_user_login</span></td><td style="width: 215px"><span class="inline-wrap">UserLoginListener</span></td><td style="width: 227px"><span class="inline-wrap">学员登录成功</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_user_logout</span></td><td style="width: 215px"><span class="inline-wrap">UserLogoutListener</span></td><td style="width: 227px"><span class="inline-wrap">学员登出成功</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_admin_user_login</span></td><td style="width: 215px"><span class="inline-wrap">AdminUserLoginListener</span></td><td style="width: 227px"><span class="inline-wrap">管理员登录:记录登录信息</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap">学员</span></td><td style="width: 168px"><span class="inline-wrap">topic_user_destroy</span></td><td style="width: 215px"><span class="inline-wrap">UserDestroyListener</span></td><td style="width: 227px"><span class="inline-wrap">学员删除,学员关联数据删除</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_user_learn_course_updated</span></td><td style="width: 215px"><span class="inline-wrap">UserLearnCourseUpdateListener</span></td><td style="width: 227px"><span class="inline-wrap">统计课程观看记录和时长</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_department_destroy</span></td><td style="width: 215px"><span class="inline-wrap">DepartmentDestroyListener</span></td><td style="width: 227px"><span class="inline-wrap">部门删除,关联删除:部门-学员关联信息删除</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap">课程</span></td><td style="width: 168px"><span class="inline-wrap">topic_course_msg_send</span></td><td style="width: 215px"><span class="inline-wrap">CourseListener</span></td><td style="width: 227px"><span class="inline-wrap">新增课程向学员推送课程消息</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_course_hour_created</span></td><td style="width: 215px"><span class="inline-wrap">CourseHourCreatedListener</span></td><td style="width: 227px"><span class="inline-wrap">课时新建:新增课时信息</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_course_destroy</span></td><td style="width: 215px"><span class="inline-wrap">CourseDestroyListener</span></td><td style="width: 227px"><span class="inline-wrap">课程删除,课程关联数据删除</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_course_chapter_destroy</span></td><td style="width: 215px"><span class="inline-wrap">CourseChapterDestroyListener</span></td><td style="width: 227px"><span class="inline-wrap">课程删除</span><span class="inline-wrap">关联删除</span><span class="inline-wrap">:课程-章节关联数据</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_course_teacher_destroy</span></td><td style="width: 215px"><span class="inline-wrap">TeacherDestroyListener</span></td><td style="width: 227px"><span class="inline-wrap">讲师删除,关联删除:课程-讲师关联数据</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap">考试</span></td><td style="width: 168px"><span class="inline-wrap">topic_question_saved_or_updated</span></td><td style="width: 215px"><span class="inline-wrap">QuestionListener</span></td><td style="width: 227px"><span class="inline-wrap">试题新建、修改:更新题库试题数量</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_paper_record</span></td><td style="width: 215px"><span class="inline-wrap">PaperListener</span></td><td style="width: 227px"><span class="inline-wrap">考试完成:生成课时记录、任务记录</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_study_task_user_save</span></td><td style="width: 215px"><span class="inline-wrap">StudyTaskListener</span></td><td style="width: 227px"><span class="inline-wrap">学习任务新建、修改:</span><span class="inline-wrap">学员任务总进度汇总</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap">通讯录</span></td><td style="width: 168px"><span class="inline-wrap">topic_wechatwork_synch</span></td><td style="width: 215px"><span class="inline-wrap">WechatWorkListener</span></td><td style="width: 227px"><span class="inline-wrap">企业微信通讯录同步</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_feishu_synch</span></td><td style="width: 215px"><span class="inline-wrap">FeiShuListener</span></td><td style="width: 227px"><span class="inline-wrap">飞书通讯录同步</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_dingtalk_synch</span></td><td style="width: 215px"><span class="inline-wrap">DingtalkListener</span></td><td style="width: 227px"><span class="inline-wrap">钉钉通讯录同步</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_ldap_synch</span></td><td style="width: 215px"><span class="inline-wrap">LDAPSyncListener</span></td><td style="width: 227px"><span class="inline-wrap">LDAP<span class="jill"></span>通讯录同步</span></td></tr><tr><td style="width: 81px"><span class="inline-wrap"></span><br/></td><td style="width: 168px"><span class="inline-wrap">topic_msg_send</span></td><td style="width: 215px"><span class="inline-wrap">SynchSendMsgListener</span></td><td style="width: 227px"><span class="inline-wrap">推送指派通知、督促通知</span></td></tr></tbody></table></div><h4 id="766ffocMMgJuvdxCqJcGpk" class="wolai-block"><span class="inline-wrap">4.2.2 国际化</span></h4><div id="b2VyJtgYzfWf9aqmWT74dR" class="wolai-block wolai-text"><div><span class="inline-wrap">PlayEdu<span class="jill"></span>支持文本内容(内置的文案模板、消息提醒等)国际化,目前支持简体中文和繁体中文。因为已经实现了国际化的流程,因此扩展英文等其它语言也比较方便(引入一份新语言的翻译内容即可)。</span></div></div><ul class="wolai-block"><li id="dmbao2xoWJJTfX5KQk7REV"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">AcceptHeaderLocaleResolver 默认解析器,通过请求头的 Accept-Language 字段来判断当前请求所属的环境的,默认是简体中文</span></li><li id="5RmZ1j3UgkVE7PEcwN4jrV"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">配置国际化资源文件路径</span></li></ul><code-block id="mQPFJ1JXeSCstLhx3nrztC" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre>spring<span class="token operator">:</span>
messages<span class="token operator">:</span>
basename<span class="token operator">:</span> i18n<span class="token operator">/</span>messages
</pre></div></code-block><ul class="wolai-block"><li id="ptPqGBqrcUPPViJU6NDWAo"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">创建国际化资源文件<span class="jill"></span>messages.properties<span class="jill"></span>不带后缀为默认语言资源</span></li></ul><div id="dZk47LPx59fp5q9yncTEFH" class="wolai-block"><figure class="wolai-center" style="width: 358px; flex-direction: column"><img src="media/image_6.png" style="width: 100%"/></figure></div><ul class="wolai-block"><li id="uQXSiN47Tt2xPNfhE4LF8h"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">获取国际化文本,下面是封装的获取工具类</span><code-block id="dycdJGeoLRHCbjTCMrMc8X" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MessageUtils</span> <span class="token punctuation">{</span>
<span class="token doc-comment comment">/**
* 根据消息键和参数 获取消息 委托给spring messageSource
*
* <span class="token keyword">@param</span> <span class="token parameter">code</span> 消息键
* <span class="token keyword">@param</span> <span class="token parameter">args</span> 参数
* <span class="token keyword">@return</span> 获取国际化翻译值
*/</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">message</span><span class="token punctuation">(</span><span class="token class-name">String</span> code<span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token class-name">MessageSource</span> messageSource <span class="token operator">=</span> <span class="token class-name">SpringUtil</span><span class="token punctuation">.</span><span class="token function">getBean</span><span class="token punctuation">(</span><span class="token class-name">MessageSource</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> messageSource<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span>code<span class="token punctuation">,</span> args<span class="token punctuation">,</span> <span class="token class-name">LocaleContextHolder</span><span class="token punctuation">.</span><span class="token function">getLocale</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>code<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre></div></code-block></li><li id="tz5pYqYd8qJ4U59BdzmnCf"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">代码中实现国际化操作</span></li></ul><code-block id="4QgPn3WRu5V7AbuokUHmQg" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre><span class="token class-name">MessageUtils</span><span class="token punctuation">.</span><span class="token function">message</span><span class="token punctuation">(</span><span class="token string">"测试"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></div></code-block></li></ul></h4><h4 id="83dkFAApQtkkpWhi7258wk" class="wolai-block"><span class="inline-wrap">4.2.3 用户认证</span></h4><blockquote id="3YKy8xi32Ma6Fdf8oWDgsj" class="wolai-block"><span class="inline-wrap">PlayEdu 的用户认证方案用的是</span><span class="inline-wrap"><code>sa-token</code></span><span class="inline-wrap">提供的。底层的技术原理的话其实就是</span><span class="inline-wrap"><code>JWT</code></span><span class="inline-wrap">也就是 </span><span class="inline-wrap"><code>token</code></span><span class="inline-wrap">校验方式。</span><span class="inline-wrap"><code>JWT</code></span><span class="inline-wrap"> 的认证原理是线下存储认证</span><span class="inline-wrap"><code>token</code></span><span class="inline-wrap"></span><span class="inline-wrap"><code>token</code></span><span class="inline-wrap">内容经过对称加密之后分发给终端用户)。因此,要实现认证用户的主动下线和登出操作的话,需要配合</span><span class="inline-wrap"><code>Redis</code></span><span class="inline-wrap">实现。之所以使用</span><span class="inline-wrap"><code>Redis</code></span><span class="inline-wrap">存储</span><span class="inline-wrap"><code>token</code></span><span class="inline-wrap">黑名单数据主要是考虑到应用的集群部署。</span></blockquote><div id="eKRZTF4aQfERvExBNxbRKa" class="wolai-block wolai-text"><div><span class="inline-wrap">下面是</span><span class="inline-wrap"><code>sa-token</code></span><span class="inline-wrap">配置说明:</span></div></div><code-block id="7ghfqCLRtkkU8cNHzJsy93" class="wolai-block"><div class="wolai-pre"><div data-lang="YAML" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token key atrule">sa-token</span><span class="token punctuation">:</span>
<span class="token key atrule">token-name</span><span class="token punctuation">:</span> <span class="token string">"Authorization"</span> <span class="token comment"># 也就是对应HTTP头的属性名</span>
<span class="token key atrule">timeout</span><span class="token punctuation">:</span> <span class="token number">1296000</span> <span class="token comment">#token有效期[单位:秒,默认15天]</span>
<span class="token key atrule">is-concurrent</span><span class="token punctuation">:</span> <span class="token boolean important">false</span> <span class="token comment">#限制同时登录,false就是一个账号只允许一台设备登录</span>
<span class="token key atrule">jwt-secret-key</span><span class="token punctuation">:</span> <span class="token string">"playeduxyz"</span> <span class="token comment"># jwt秘钥用户加密分发的token内容请尽量设置复杂</span>
<span class="token key atrule">token-prefix</span><span class="token punctuation">:</span> <span class="token string">"Bearer"</span> <span class="token comment"># token前缀。psAuthorization: Bearer {token}</span>
<span class="token key atrule">is-log</span><span class="token punctuation">:</span> <span class="token boolean important">false</span> <span class="token comment"># 是否输出操作日志</span></pre></div></code-block><div id="gwpPGnAdVDsBPKQBrkwiXZ" class="wolai-block wolai-text"><div><span class="inline-wrap">4.2.3.1<span class="jill"></span>后台管理员认证流程</span></div></div><div id="iHSoBNpqb6taGNo9UST4wC" class="wolai-block wolai-text"><div><span class="inline-wrap">管理员的请求</span><span class="inline-wrap"><code>/backend/**</code></span><span class="inline-wrap">由自定义拦截器</span><span class="inline-wrap"><code>AdminInterceptor</code></span><span class="inline-wrap">拦截。下面是一些常用的方法:</span></div></div><code-block id="oQx4twpWWXbD7h4a5kiDdA" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token comment">// 1.判断管理员是否登录</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>authService<span class="token punctuation">.</span><span class="token function">check</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">responseTransform</span><span class="token punctuation">(</span>response<span class="token punctuation">,</span> <span class="token number">401</span><span class="token punctuation">,</span> <span class="token string">"请登录"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 1.具体实现</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">check</span><span class="token punctuation">(</span><span class="token class-name">String</span> prv<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 获取当前会话是否已经登录, 返回true或false</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">isLogin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// </span>
<span class="token class-name">String</span> tokenPrv <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">getExtra</span><span class="token punctuation">(</span><span class="token string">"prv"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> prv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>tokenPrv<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 2.获取当前登录管理员的ID</span>
authService<span class="token punctuation">.</span><span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 2.具体实现 </span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token class-name">Integer</span> <span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">getLoginIdAsInt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 3.根据管理员ID获取管理员信息包含邮箱名称等信息</span>
<span class="token class-name">AdminUser</span> adminUser <span class="token operator">=</span> adminUserService<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>authService<span class="token punctuation">.</span><span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 4.根据管理员ID获取所有权限信息</span>
backendBus<span class="token punctuation">.</span><span class="token function">adminUserPermissions</span><span class="token punctuation">(</span>adminUser<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// 4.具体实现</span>
<span class="token keyword">public</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Boolean</span><span class="token punctuation">></span></span> <span class="token function">adminUserPermissions</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> userId<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 读取超级管理角色</span>
<span class="token class-name">AdminRole</span> superRole <span class="token operator">=</span> adminRoleService<span class="token punctuation">.</span><span class="token function">getBySlug</span><span class="token punctuation">(</span><span class="token class-name">BackendConstant</span><span class="token punctuation">.</span><span class="token constant">SUPER_ADMIN_ROLE</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Boolean</span><span class="token punctuation">></span></span> permissions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 根据管理员ID获取所属角色</span>
<span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> roleIds <span class="token operator">=</span> adminUserService<span class="token punctuation">.</span><span class="token function">getRoleIdsByUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>roleIds<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> permissions<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> permissionIds<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>roleIds<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>superRole<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 包含超级管理角色的话返回全部权限</span>
permissionIds <span class="token operator">=</span> permissionService<span class="token punctuation">.</span><span class="token function">allIds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// 根据相应的roleIds读取权限</span>
permissionIds <span class="token operator">=</span> adminRoleService<span class="token punctuation">.</span><span class="token function">getPermissionIdsByRoleIds</span><span class="token punctuation">(</span>roleIds<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>permissionIds<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> permissions<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> permissionService<span class="token punctuation">.</span><span class="token function">getSlugsByIds</span><span class="token punctuation">(</span>permissionIds<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 5.当前请求上下文下获取已登录管理员的id和详细信息</span>
<span class="token class-name">BCtx</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取id</span>
<span class="token class-name">BCtx</span><span class="token punctuation">.</span><span class="token function">getAdminUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取adminUser</span></pre></div></code-block><div id="v58WiZvBLHQMQu6Fqo8U4p" class="wolai-block wolai-text"><div><span class="inline-wrap">4.2.3.2 学员</span><span class="inline-wrap">用户认证流程</span></div></div><div id="3ptvK8BUCZxtGR7tqptNrs" class="wolai-block wolai-text"><div><span class="inline-wrap">学员端口的请求 </span><span class="inline-wrap"><code>/api/v1/**</code></span><span class="inline-wrap">由自定义拦截器</span><span class="inline-wrap"><code>FrontInterceptor</code></span><span class="inline-wrap">拦截,下面是常用的方法:</span></div></div><code-block id="9cYSzDgRbZZ3Hv4hkvsZJ4" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token comment">// 1.判断学员是否登录,未登录返回错误信息</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>authService<span class="token punctuation">.</span><span class="token function">check</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">responseTransform</span><span class="token punctuation">(</span>response<span class="token punctuation">,</span> <span class="token number">401</span><span class="token punctuation">,</span> <span class="token string">"请登录"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 1.具体实现</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">check</span><span class="token punctuation">(</span><span class="token class-name">String</span> prv<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 获取当前会话是否已经登录, 返回true或false</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">isLogin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// </span>
<span class="token class-name">String</span> tokenPrv <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">getExtra</span><span class="token punctuation">(</span><span class="token string">"prv"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> prv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>tokenPrv<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 2.获取当前登录学员的ID</span>
authService<span class="token punctuation">.</span><span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 2.具体实现</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token class-name">Integer</span> <span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">StpUtil</span><span class="token punctuation">.</span><span class="token function">getLoginIdAsInt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 3.根据学员ID获取学员信息包含邮箱名称等信息</span>
<span class="token class-name">User</span> user <span class="token operator">=</span> userService<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>authService<span class="token punctuation">.</span><span class="token function">userId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 4.当前请求上下文下获取已登录管理员的id和详细信息</span>
<span class="token class-name">FCtx</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取id</span>
<span class="token class-name">FCtx</span><span class="token punctuation">.</span><span class="token function">getAdminUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取user</span></pre></div></code-block><h4 id="hBYQNALm2ZPmaQ9vcE16Tm" class="wolai-block"><span class="inline-wrap">4.2.4 权限认证</span></h4><ol class="wolai-block"><li id="ft7cBXjcshCL2bqVu2n9Hw"><div class="marker"></div><span class="inline-wrap">权限类型:</span><ol class="wolai-block"><li id="82PKtHJcYvDZ75nxDqBAhM"><div class="marker"></div><span class="inline-wrap">操作权限(</span><span class="inline-wrap"><code>action</code></span><span class="inline-wrap">):用于行为操作(增删改查)的权限控制</span></li><li id="xa2FEabnHE2fhpMf9ZYqzB"><div class="marker"></div><span class="inline-wrap">数据权限(</span><span class="inline-wrap"><code>data</code></span><span class="inline-wrap">):用于业务数据(如:学员邮箱字段)的权限控制</span></li></ol></li><li id="imF26hCZTRMPcvVjWHRpQL"><div class="marker"></div><span class="inline-wrap">表结构说明</span></li></ol><blockquote id="4R4LhtPurJxyXgv9hQaQ6b" class="wolai-block"><span class="inline-wrap">内置的权限数据在</span><span class="inline-wrap"><code>playedu-system</code></span><span class="inline-wrap">模块</span><span class="inline-wrap"><code>AdminPermissionCheck</code></span><span class="inline-wrap">文件,系统每次启动都会自动将最新的权限数据同步到数据库中。具体请见本册后面介绍的</span><span class="inline-wrap"><code>内置数据</code></span><span class="inline-wrap">章节。</span></blockquote><ul class="wolai-block"><li id="bqNANJE8YBN3jeMuv61UyX"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">权限表</span><span class="inline-wrap"><code>admin_permissions</code></span></li></ul><div id="8PsAjBpNqiABFC6wVddGmJ" class="wolai-block wolai-simple-table"><table><thead><tr><th style="width: 120px"><span class="inline-wrap">名称</span></th><th style="width: 100px"><span class="inline-wrap">数据类型</span></th><th style="width: 239px"><span class="inline-wrap">说明</span></th></tr></thead><tbody><tr><td><span class="inline-wrap">id</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">主键</span></td></tr><tr><td><span class="inline-wrap">type</span></td><td><span class="inline-wrap">varchar</span></td><td><span class="inline-wrap">类型[行为:action,数据:data]</span></td></tr><tr><td><span class="inline-wrap">group_name</span></td><td><span class="inline-wrap">varchar</span></td><td><span class="inline-wrap">分组</span></td></tr><tr><td><span class="inline-wrap">sort</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">升序(控制在前台展示的排序)</span></td></tr><tr><td><span class="inline-wrap">name</span></td><td><span class="inline-wrap">varchar</span></td><td><span class="inline-wrap">权限名</span></td></tr><tr><td><span class="inline-wrap">slug</span></td><td><span class="inline-wrap">varchar</span></td><td><span class="inline-wrap">slug唯一特征值</span></td></tr><tr><td><span class="inline-wrap">created_at</span></td><td><span class="inline-wrap">timestamp</span></td><td><span class="inline-wrap">创建时间</span></td></tr></tbody></table></div><ul class="wolai-block"><li id="jkNdSUwccd7DXaeCQRppUT"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">管理员(</span><span class="inline-wrap"><code>admin_users</code></span><span class="inline-wrap">)和角色(</span><span class="inline-wrap"><code>admin_roles</code></span><span class="inline-wrap">)关联的中间表(多对多关联)</span><span class="inline-wrap"><code>admin_user_role</code></span></li></ul><div id="dQtpNvZnnQD9ThYUHAX9rk" class="wolai-block wolai-simple-table"><table><thead><tr><th style="width: 100px"><span class="inline-wrap">名称</span></th><th style="width: 100px"><span class="inline-wrap">数据类型</span></th><th style="width: 100px"><span class="inline-wrap">说明</span></th></tr></thead><tbody><tr><td><span class="inline-wrap">role_id</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">角色<span class="jill"></span>ID</span></td></tr><tr><td><span class="inline-wrap">perm_id</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">权限<span class="jill"></span>ID</span></td></tr></tbody></table></div><ul class="wolai-block"><li id="1K3FCy2avbQ3nryXjG2zsS"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">角色(</span><span class="inline-wrap"><code>admin_roles</code></span><span class="inline-wrap">)与权限(</span><span class="inline-wrap"><code>admin_permissions</code></span><span class="inline-wrap">)关联的中间表(多对多关联)</span><span class="inline-wrap"><code>admin_role_permission</code></span></li></ul><div id="tJpenookjfu1G3XFAGJ9R9" class="wolai-block wolai-simple-table"><table><thead><tr><th style="width: 100px"><span class="inline-wrap">名称</span></th><th style="width: 100px"><span class="inline-wrap">数据类型</span></th><th style="width: 100px"><span class="inline-wrap">说明</span></th></tr></thead><tbody><tr><td><span class="inline-wrap">role_id</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">角色<span class="jill"></span>ID</span></td></tr><tr><td><span class="inline-wrap">perm_id</span></td><td><span class="inline-wrap">int</span></td><td><span class="inline-wrap">权限<span class="jill"></span>ID</span></td></tr></tbody></table></div><ol class="wolai-block"><li id="c7VM2XgyanDikfZaZ858cT"><div class="marker"></div><span class="inline-wrap">新增权限数据</span></li></ol><div id="tBVnbaCfBatGsn4HdtrtMy" class="wolai-block wolai-text"><div><span class="inline-wrap"></span><span class="inline-wrap"><code>AdminPermissionCheck</code></span><span class="inline-wrap">文件,按照权限对象格式新增权限</span></div></div><code-block id="sxfMCxe7prexsu9RpKdT8t" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"> <span class="token function">put</span><span class="token punctuation">(</span>
<span class="token comment">// 权限类型[行为:action,数据:data] </span>
<span class="token class-name">BPermissionConstant</span><span class="token punctuation">.</span><span class="token constant">TYPE_DATA</span><span class="token punctuation">,</span>
<span class="token comment">// 权限对象</span>
<span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token punctuation">{</span>
<span class="token function">put</span><span class="token punctuation">(</span>
<span class="token string">"权限分组"</span><span class="token punctuation">,</span> <span class="token comment">// 权限分组</span>
<span class="token keyword">new</span> <span class="token class-name">AdminPermission</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
<span class="token keyword">new</span> <span class="token class-name">AdminPermission</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token punctuation">{</span>
<span class="token function">setSort</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 升序</span>
<span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"权限名"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 权限名</span>
<span class="token function">setSlug</span><span class="token punctuation">(</span><span class="token string">"slug"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 权限slug</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></div></code-block><ol class="wolai-block"><li id="j4CqEa1fUQe51fjFdJQVoh"><div class="marker"></div><span class="inline-wrap">通过角色编辑接口,来完成角色与权限数据的关联</span></li></ol><code-block id="vJNV1r3MrzgqkrJTVhhvZ1" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre>roleService<span class="token punctuation">.</span><span class="token function">updateWithPermissionIds</span><span class="token punctuation">(</span>role<span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">getPermissionIds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre></div></code-block><ol class="wolai-block"><li id="rnk3RAd3iZfvF1uQnasPre"><div class="marker"></div><span class="inline-wrap">行为权限校验使用:使用注解</span><span class="inline-wrap"><code>@BackendPermission(slug = &quot;slug&quot;)</code></span><span class="inline-wrap">标记控制器方法,会校验调用者是否具有指定权限</span></li></ol><code-block id="jxNQ96JYtWJ74W2wYDjexg" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token comment">// 下面注解可以加到 Controller 的 method 上实现行为权限的控制</span>
<span class="token annotation punctuation">@BackendPermission</span><span class="token punctuation">(</span>slug <span class="token operator">=</span> <span class="token class-name">BPermissionConstant</span><span class="token punctuation">.</span><span class="token constant">ADMIN_ROLE</span><span class="token punctuation">)</span>
<span class="token comment">// 行为权限控制的具体实现原理</span>
<span class="token annotation punctuation">@Around</span><span class="token punctuation">(</span><span class="token string">"doPointcut()"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">Object</span> <span class="token function">doAround</span><span class="token punctuation">(</span><span class="token class-name">ProceedingJoinPoint</span> joinPoint<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Throwable</span> <span class="token punctuation">{</span>
<span class="token class-name">MethodSignature</span> signature <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">MethodSignature</span><span class="token punctuation">)</span> joinPoint<span class="token punctuation">.</span><span class="token function">getSignature</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">BackendPermission</span> middleware <span class="token operator">=</span> signature<span class="token punctuation">.</span><span class="token function">getMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAnnotation</span><span class="token punctuation">(</span><span class="token class-name">BackendPermission</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Integer</span> adminUserId <span class="token operator">=</span> <span class="token class-name">BCtx</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Boolean</span><span class="token punctuation">></span></span> permissions <span class="token operator">=</span> backendBus<span class="token punctuation">.</span><span class="token function">adminUserPermissions</span><span class="token punctuation">(</span>adminUserId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>permissions<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>middleware<span class="token punctuation">.</span><span class="token function">slug</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">JsonResponse</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"权限不足"</span><span class="token punctuation">,</span> <span class="token number">403</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> joinPoint<span class="token punctuation">.</span><span class="token function">proceed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></pre></div></code-block><div id="7wEoDVhsYPtCP8GDNiTWmv" class="wolai-block wolai-text"><div><span class="inline-wrap">6.数据权限控制:通过给</span><span class="inline-wrap"><code>Model</code></span><span class="inline-wrap">增加</span><span class="inline-wrap"><code>@JsonGetter</code></span><span class="inline-wrap">注解方法,在该方法内部实现判断当前管理员是否具有指定的数据权限,如果没有的话则返回打码后的数据。</span></div></div><code-block id="bo8HHYtfBcMA1n9ovfNVuS" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token annotation punctuation">@TableName</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"admin_users"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@Data</span>
<span class="token annotation punctuation">@Slf4j</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AdminUser</span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span>
<span class="token annotation punctuation">@JsonGetter</span><span class="token punctuation">(</span><span class="token string">"email"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">transformEmail</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">BackendBus</span><span class="token punctuation">.</span><span class="token function">valueHidden</span><span class="token punctuation">(</span>
<span class="token class-name">BPermissionConstant</span><span class="token punctuation">.</span><span class="token constant">DATA_ADMIN_EMAIL</span><span class="token punctuation">,</span>
<span class="token class-name">BackendConstant</span><span class="token punctuation">.</span><span class="token constant">PRIVACY_FIELD_TYPE_EMAIL</span><span class="token punctuation">,</span>
email<span class="token punctuation">,</span>
<span class="token class-name">CommonConstant</span><span class="token punctuation">.</span><span class="token constant">ZERO</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</pre></div></code-block><h4 id="wrEy1TcNAx7N5Y5fh6qMR2" class="wolai-block"><span class="inline-wrap">4.2.5 系统初始化</span></h4><div id="38A4ihosYoV11j6Qs3LNkH" class="wolai-block wolai-text"><div><span class="inline-wrap">PlayEdu 利用 SpringBoot 的 </span><span class="inline-wrap"><code>CommandLineRunner</code></span><span class="inline-wrap"> 机制实现了在系统启动的时候完成数据库表的自动更新和系统初始化数据自动同步到数据库中。通过 </span><span class="inline-wrap"><code>@Order</code></span><span class="inline-wrap">注解实现各个 </span><span class="inline-wrap"><code>CommandLineRunner</code></span><span class="inline-wrap">的执行顺序,这样确保数据库表的初始化是最先执行的。下面是 PlayEdu 内置的几个 </span><span class="inline-wrap"><code>CommandLineRunner</code></span><span class="inline-wrap">的启动那个顺序:</span></div></div><blockquote id="sHkma5DqCiusK5DsC3rfVx" class="wolai-block"><span class="inline-wrap">注意,这些 </span><span class="inline-wrap"><code>Checks</code></span><span class="inline-wrap"> 的文件位置在 </span><span class="inline-wrap"><code>playedu-system</code></span><span class="inline-wrap"> 模块下的 </span><span class="inline-wrap"><code>checks</code></span><span class="inline-wrap">目录。</span></blockquote><div id="kvBthdWCk3raysXRE5XLtJ" class="wolai-block"><figure class="wolai-center" style="width: 274.5px; flex-direction: column"><img src="media/image_7.png" style="width: 100%"/></figure></div><div id="5gWBbQYoSsbPVB36odL7KZ" class="wolai-block wolai-simple-table"><table><thead><tr><th style="width: 243px"><span class="inline-wrap">Check</span></th><th style="width: 228px"><span class="inline-wrap">说明</span></th></tr></thead><tbody><tr><td><span class="inline-wrap"><code>MigrationCheck</code></span></td><td><span class="inline-wrap">数据库表同步</span></td></tr><tr><td><span class="inline-wrap"><code>AppConfigCheck</code></span></td><td><span class="inline-wrap">系统默认配置同步</span></td></tr><tr><td><span class="inline-wrap"><code>AdminPermissionCheck</code></span></td><td><span class="inline-wrap">系统管理角色权限同步</span></td></tr><tr><td><span class="inline-wrap"><code>SystemDataCheck</code></span></td><td><span class="inline-wrap">系统初始化数据同步</span></td></tr><tr><td><span class="inline-wrap"><code>UpgradeCheck</code></span></td><td><span class="inline-wrap">系统版本升级逻辑</span></td></tr></tbody></table></div><div id="2Ap8kVRFwy9iv2ftQRRdpA" class="wolai-block wolai-text"><div><span class="inline-wrap"></span><br/></div></div><h4 id="8fWNmCTG1rjpWx7ZyCfJw8" class="wolai-block"><span class="inline-wrap">4.2.6 代码格式化</span></h4><div id="w48APZgvSU2TeM9aJBwBFB" class="wolai-block wolai-text"><div><span class="inline-wrap">Spotless 是支持多种语言的代码格式化工具(自动或手动方式均可),自动为代码添加 </span><span class="inline-wrap"><code>licenseHeader</code></span><span class="inline-wrap"> 和格式化代码。在项目的</span><span class="inline-wrap"><code>pom</code></span><span class="inline-wrap">文件中添加配置</span></div></div><code-block id="cYsBrGiMpaz76J6Sa1jKJw" class="wolai-block"><div class="wolai-pre"><div data-lang="XML" class="marker"></div><pre style="white-space: pre-wrap; word-break: break-all"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.diffplug.spotless<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spotless-maven-plugin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.36.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>configuration</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>java</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>googleJavaFormat</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.16.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span>AOSP<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>reflowLongStrings</span><span class="token punctuation">></span></span>true<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>reflowLongStrings</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>googleJavaFormat</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>licenseHeader</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>file</span><span class="token punctuation">></span></span>license-header.txt<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>file</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>licenseHeader</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>java</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>configuration</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span></pre></div></code-block><ul class="wolai-block"><li id="9hkiTY7j8RiLueqbVvAEW9"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">手动执行代码格式化</span></li></ul><div id="wdzXPYG9AeQR4UqPo4dtsJ" class="wolai-block wolai-text"><div><span class="inline-wrap">我们就可以执行以下命令来检查 Java 代码是否符合规范并进行格式化:</span></div></div><code-block id="uXbakcT8r6dRT9MDgsj6zk" class="wolai-block"><div class="wolai-pre"><div data-lang="Bash" class="marker"></div><pre><span class="token comment"># 查看哪些代码不符合代码格式</span>
./mvnw spotless:check
<span class="token comment"># 代码格式化</span>
./mvnw spotless:apply
</pre></div></code-block><ul class="wolai-block"><li id="2WN33cCdycxknJofduHWNW"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">在 IDEA 中使用 Maven 插件来执行这些操作。只需点击一下,即可完成整个过程。</span></li></ul><div id="8ohgNMAcbStSB9aY3nALLM" class="wolai-block"><figure class="wolai-center" style="width: 233px; flex-direction: column"><img src="media/image_8.png" style="width: 100%"/></figure></div><h4 id="29FtWJ7X2Xao3HogyKuFaH" class="wolai-block"><span class="inline-wrap">4.2.7 定时任务</span></h4><div id="wMXBKR8mq2TBzVC7x2Dxj7" class="wolai-block wolai-text"><div><span class="inline-wrap">Playedu<span class="jill"></span>使用<span class="jill"></span>quartz<span class="jill"></span>实现分布式定时任务框架,系统做好了封装,只需要简单的步骤即可完成定时任务功能</span></div></div><ul class="wolai-block"><li id="fhxmmZoisXgiN2QBQE2B5R"><div class="marker"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 14.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path></svg></div><span class="inline-wrap">定义一个</span><span class="inline-wrap">TestJob </span></li></ul><code-block id="4gmDvjkDVANfgXrBJdykGq" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre><span class="token comment">// 禁止并发执行(Quartz不要并发地执行同一个job定义这里指一个job类的多个实例)</span>
<span class="token annotation punctuation">@DisallowConcurrentExecution</span>
<span class="token annotation punctuation">@Slf4j</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestJob</span> <span class="token keyword">extends</span> <span class="token class-name">QuartzJobBean</span> <span class="token punctuation">{</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">executeInternal</span><span class="token punctuation">(</span><span class="token class-name">JobExecutionContext</span> context<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">JobExecutionException</span> <span class="token punctuation">{</span>
<span class="token comment">// 执行业务逻辑</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre></div></code-block><div id="8sT2MR4zTdNSnnyCmW5Mka" class="wolai-block wolai-text"><div><span class="inline-wrap"><span class="jill"></span>QuartzTasks<span class="jill"></span>文件中的<span class="jill"></span>addQuartzJobs()方法新增上面创建的<span class="jill"></span>TestJob </span></div></div><code-block id="mMnaTTKBgR7Nhi74niCzWq" class="wolai-block"><div class="wolai-pre"><div data-lang="Java" class="marker"></div><pre><span class="token comment">// 添加定时任务</span>
<span class="token function">addJob</span><span class="token punctuation">(</span>
scheduler<span class="token punctuation">,</span> <span class="token comment">// 任务调度器</span>
<span class="token class-name">TestJob</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token comment">// 上面创建的job</span>
<span class="token string">"test"</span><span class="token punctuation">,</span> <span class="token comment">// 分组名称</span>
<span class="token string">"TestJob"</span><span class="token punctuation">,</span> <span class="token comment">// 任务名称</span>
<span class="token string">"0/30 * * * * ?"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 调度频率 每30秒执行一次</span></pre></div></code-block><div id="ggAmZWtGx1VgT4aAjgaC3K" class="wolai-block wolai-text"><div><span class="inline-wrap">这样我们就实现了<span class="jill"></span>TestJob 定时任务的创建。</span></div></div><div id="aXQ9MXMNzLb5tqhg66BBVY" class="wolai-block wolai-text"><div><span class="inline-wrap"></span><br/></div></div><h3 id="79rbe6B6k5DgfVcWQmGsyk" class="wolai-block"><span class="inline-wrap">4.3 程序依赖信息</span></h3><div id="rJi3dW116HCSp1vWZkSf2V" class="wolai-block wolai-simple-table"><table><tbody><tr><td style="width: 156px"><span class="inline-wrap">插件</span></td><td style="width: 279px"><span class="inline-wrap">地址</span></td><td style="width: 126px"><span class="inline-wrap">说明</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-web</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://spring.io/projects/spring-boot"><span>https://spring.io/projects/spring-boot</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">Web<span class="jill"></span>场景启动器</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-aop</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://spring.io/projects/spring-boot"><span>https://spring.io/projects/spring-boot</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">实现切面编程</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-data-redis</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://spring.io/projects/spring-data-redis"><span>https://spring.io/projects/spring-data-redis</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">集成<span class="jill"></span>redis</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-test</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://spring.io/projects/spring-boot"><span>https://spring.io/projects/spring-boot</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">单元测试</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap"><a href="https://github.com/baomidou/mybatis-plus"><span>mybatis-plus-boot-starter</span></a></span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://github.com/baomidou/mybatis-plus"><span>https://github.com/baomidou/mybatis-plus</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">集成</span><span class="inline-wrap"><a href="https://github.com/baomidou/mybatis-plus"><span>mybatis-plus</span></a></span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-validation</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://spring.io/projects/spring-boot"><span>https://spring.io/projects/spring-boot</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">表单验证</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">gson</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://mvnrepository.com/artifact/com.google.code.gson/gson"><span>https://mvnrepository.com/artifact/com.google.code.gson/gson</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">JSON<span class="jill"></span>序列化及解析</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">lombok</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://projectlombok.org/"><span>https://projectlombok.org</span></a></span></td><td style="width: 126px"><span class="inline-wrap">Java 类库</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">mysql-connector-j</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://www.mysql.com/"><span>https://www.mysql.com</span></a></span></td><td style="width: 126px"><span class="inline-wrap">集成<span class="jill"></span>mysql</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spotless</span></td><td style="width: 279px"><span class="inline-wrap">https://github.com/diffplug/spotless/tree/main/plugin-maven/</span></td><td style="width: 126px"><span class="inline-wrap">代码格式化</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">sa-token-spring-boot3-starter</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://sa-token.cc/"><span>https://sa-token.cc</span></a></span></td><td style="width: 126px"><span class="inline-wrap">权限认证</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">sa-token-dao-redis-jackson</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://sa-token.cc/"><span>https://sa-token.cc</span></a></span></td><td style="width: 126px"><span class="inline-wrap">Sa-Token 整合 Redis</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">sa-token-jwt</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://sa-token.cc/"><span>https://sa-token.cc</span></a></span></td><td style="width: 126px"><span class="inline-wrap">Sa-Token 整合 jwt</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">poi-ooxml</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://poi.apache.org/"><span>https://poi.apache.org</span></a></span></td><td style="width: 126px"><span class="inline-wrap">文档处理</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">commons-lang3</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://commons.apache.org/proper/commons-lang/"><span>https://commons.apache.org/proper/commons-lang/</span></a></span></td><td style="width: 126px"><span class="inline-wrap">常用类库</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">commons-collections4</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://commons.apache.org/proper/commons-collections/"><span>https://commons.apache.org/proper/commons-collections/</span></a></span></td><td style="width: 126px"><span class="inline-wrap">集合类库</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">commons-io</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://commons.apache.org/proper/commons-io/"><span>https://commons.apache.org/proper/commons-io/</span></a></span></td><td style="width: 126px"><span class="inline-wrap">文件 IO<span class="jill"></span>类库</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">minio</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://github.com/minio/minio-java"><span>https://github.com/minio/minio-java</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap">分布式存储</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">aws-java-sdk-s3</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://aws.amazon.com/sdkforjava"><span>https://aws.amazon.com/sdkforjava</span></a></span><span class="inline-wrap">/</span></td><td style="width: 126px"><span class="inline-wrap"><a href="https://so.csdn.net/so/search?q=对象存储&amp;spm=1001.2101.3001.7020"><span>对象存储</span></a></span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-boot-starter-quartz</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz"><span>https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz</span></a></span></td><td style="width: 126px"><span class="inline-wrap">定时任务</span></td></tr><tr><td style="width: 156px"><span class="inline-wrap">spring-kafka</span></td><td style="width: 279px"><span class="inline-wrap"><a href="https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka"><span>https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka</span></a></span></td><td style="width: 126px"><span class="inline-wrap">消息队列</span></td></tr></tbody></table></div><div id="gYt4aJZpJ6t9mitzBv4Een" class="wolai-block wolai-text"><div></div></div></article><footer></footer></body></html>