diff --git a/.gitignore b/.gitignore
index be3e648339a52f7c7d156f52366518f79622196e..662be2ee971c50c6a21c82e0ac3d49134e0897d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,13 @@ logs
*.log
.logs
*.iws
+*.report
*.classpath
.DS_Store
+.ossutil_checkpoint
*.patch
-~$*
\ No newline at end of file
+~$*
+node_modules
+yarn.lock
+dist
+package-lock.json
diff --git a/README.md b/README.md
index 5561be798f756e00f9774f1eae5e797130ccd358..f00db2a2e48bdb6004f50b325ae8bafcdc143e98 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,19 @@
+
+
+Jboot 是一个基于 JFinal、Dubbo、Seata、Sentinel、ShardingSphere、Nacos 等开发的国产框架。
+
+其特点是:
+
+- 1、基于 JFinal 完整的 MVC + ORM 支持。
+- 2、支持多数据源、分库分表和分布式事务。
+- 3、支持 Dubbo RPC 的完整功能,有超过 1亿+ 用户产品正在使用。
+- 4、完整的单点限流和分布式限流功能
+- 5、支持基基于 Apollo 和 Nacos 的分布式配置中心
+- 6、完整的分布式缓存、分布式session、分布式附件支持
+- 7、内置功能强劲的门户网关
+- 8、完整的单元测试支持
+- 9、完善代码生成工具 和 API 文档生成工具
+- 10、Docker、K8S 友好
## 开始
@@ -8,7 +24,7 @@
io.jboot
jboot
- 3.0.1
+ 4.1.4
```
@@ -16,7 +32,7 @@
```java
@RequestMapping("/")
-public class HelloworldController extends JbootController {
+public class Helloworld extends JbootController {
public void index(){
renderText("hello world");
@@ -31,39 +47,26 @@ public class HelloworldController extends JbootController {
## 帮助文档
-- [demos](./src/test/java/io/jboot/test)
-- [安装](./doc/docs/install.md)
-- [2分钟快速开始](./doc/docs/quickstart.md)
-- [热加载](./doc/docs/hotload.md)
-- [Undertow](./doc/docs/undertow.md)
-- [配置](./doc/docs/config.md)
-- [JFinalConfig](./doc/docs/jfinalConfig.md)
-- [WebSocket](./doc/docs/websocket.md)
-- [MVC](./doc/docs/mvc.md)
-- [数据库](./doc/docs/db.md)
-- [缓存](./doc/docs/cache.md)
-- [RPC远程调用](./doc/docs/rpc.md)
-- [MQ消息队列](./doc/docs/mq.md)
-- [任务调度](./doc/docs/schedule.md)
-- [限流](./doc/docs/limit.md)
-- [监控](./doc/docs/metrics.md)
-- [序列化](./doc/docs/serialize.md)
-- [事件机制](./doc/docs/event.md)
-- [SPI扩展机制](./doc/docs/spi.md)
-- [代码生成器](./doc/docs/codegen.md)
-- [项目构建](./doc/docs/build.md)
-- [项目部署](./doc/docs/deploy.md)
-- [Jboot与Docker](./doc/docs/docker.md)
-- [1.x 升级到 2.x 教程](./doc/docs/upgrade.md)
-- [交流社区、QQ群和微信群](./doc/docs/communication.md)
-- 第三方组件的支持
- - [redis](./doc/docs/redis.md)
- - [shiro](./doc/docs/shiro.md)
- - [jwt](./doc/docs/jwt.md)
- - [swagger](./doc/docs/swagger.md)
+- 文档请访问:[www.jboot.com.cn](http://www.jboot.com.cn)
+- Demos 请访问:[这里](./src/test/java/io/jboot/test)
+## 广告
+
+- 一个好用的在线代码格式化工具:[http://www.CodeFormat.CN](http://www.codeformat.cn)
## 微信交流群
-
+
+
+
+## JbootAdmin
+
+JbootAdmin 是 Jboot 官方推出的、收费的、企业级快速开发框架,真诚的为各位开发者提供一站式、保姆式的开发服务。
+关于 JbootAdmin 的更多的功能请咨询海哥(微信:wx198819880),或请访问以下网址:
+
+[http://jboot.io/jbootadmin/feature.html](http://jboot.io/jbootadmin/feature.html)
+
+
+
+
diff --git a/changes.txt b/changes.txt
index 0222680079b80a3277f74aebfb46276e3ed4f60b..39f2a4402b19ec546894b92398ad0373104c7880 100644
--- a/changes.txt
+++ b/changes.txt
@@ -1,3 +1,1397 @@
+jboot v4.1.3 2023-06-21:
+优化:线程池同一使用 NamedThreadPools.java 进行构建
+优化:修改版错别字 taked 为 took
+优化:添加更多的扫描 jar 排除,减少启动消耗时间
+
+
+
+jboot v4.1.2 2023-06-20:
+修复:Oracle 数据库在某些极端情况下出错的问题
+
+
+
+jboot v4.1.1 2023-06-19:
+修复:修复 MQ 通知线程在高并发场景下无法及时回收的问题
+优化:升级 JFinal 等依赖到最新版本
+
+
+
+jboot v4.1.0 2023-05-12:
+新增:自定义 JFinalFilter 的支持
+优化:重命名 JbootAccessTokenCache 为 WechatAccessTokenCache
+优化:JbootAppListenerManager 中重复的 foreach 操作 感谢 @梦行
+优化:升级 Jfinal/jfinal-undertow/jsoup 等到最新版本
+
+
+
+jboot v4.0.9 2023-03-08:
+优化:JbootResourceLoader 忽略掉 windows 和 mac 下的临时文件
+优化:修改 SqlBuilder.java 的错别字
+修复:Columns.orEqs 没有添加括号导致 sql 逻辑不对的问题
+
+
+
+jboot v4.0.8 2023-02-05:
+优化:升级 ShardingJDBC 到 5.x 最新版本
+优化:升级 Columns.in 和 notIn 等方法和错别字
+优化:升级 jfinal-undertow、jackson、metrics 等到最新版本
+
+
+
+jboot v4.0.7 2023-01-06:
+优化:重构 AttachmentManager 使之更加灵活易用
+新增:StrUtil.isStartsWithAny() 方法
+新增:FileUtil.getFileMD5() 等方法
+新增:CdnUtil.appendCdnDomain() 方法
+
+
+
+jboot v4.0.6 2022-12-31:
+修复:缓存注解自动生成 key 无法支持集合参数的问题
+优化:ApplicationUtil.java 使其在 window 下输出正确的 classpath 路径
+优化:修改 ValueFilterInterceptor.java 里的错别字
+优化:JbootConfigManager.java 配置独立目录时,再次读取 jboot.properties 文件
+
+
+
+jboot v4.0.5 2022-12-29:
+新增:jboot.cache.cacheSyncMqChannel 的配置,用于对分布式缓存的 channel 进行自定义
+优化:JbootRedisCacheImpl 初始化的错误提示内容
+优化:ClassUtil.newInstance() 方法,方便对构造器进行传参
+优化:删除无用的 GenTester.java 文件
+优化:不再对 JbootSerializerManager 进行 Aop 增强
+优化:不再对 JbootmqManager 进行 Aop 增强
+优化:不再对 JbootEventManager 进行 Aop 增强
+
+
+
+jboot v4.0.2 2022-12-16:
+新增:db.each 的 sql 打印输出
+新增:通过 Controller 获取参数时,自动对参数进行 trim 操作
+新增:配置文件可以外部的任意目录
+优化:升级 jfinal 到最新版本
+
+
+
+jboot v4.0.1 2022-12-03:
+新增:新增可以通过配置取消 AOP 缓存的功能
+新增:新增 jboot.cache.useFirstLevelOnly 配置的功能,可以在分布式下只开启 1 级缓存
+优化:修改 aop action 等默认缓存时间为 10 分钟
+优化:ModelUtil.keep 方法
+优化:ObjectUti.convert 方法
+修复:new Model().use("ds").update() 在某些场景下出错的问题
+
+
+
+jboot v4.0.0 2022-11-27:
+优化:正式支持 jdk 17.x
+
+
+===================== v3.x =========================
+
+jboot v3.17.1 2022-11-13:
+优化:Columns.java 对空条件的判断
+优化:ClassScanner.java 支持扫描 jar 包里的 jar 包,感谢 @陈立刚
+优化:Redis 消息队列设置支持多个 database,感谢 @陈立刚
+
+
+
+jboot v3.17.0 2022-11-12:
+新增:自定义 Controller 缓存刷新 key 的功能
+优化:Jboot MQ 在启动和停止的时候,不对已启动进行错误抛出,方便多模块可以自由启动或停止
+优化:Jboot MQ 添加监听器的时候,自动添加 channel 信息
+优化:升级 jackson-core 等到最新版本
+
+
+
+jboot v3.16.8 2022-11-06:
+新增:Controller.getFilesOnly(Set paraNames) 方法
+优化:默认关闭缓存日志
+
+
+
+jboot v3.16.7 2022-10-31:
+新增:添加 CachePrinter 的支持,方便把缓存信息输出在控制台或者日志上
+优化:JbootResourceLoader 优化过滤掉 Windows 的临时文件
+优化:使用注解验证数据时,控制台不再抛出异常信息,只打印基本验证错误信息
+优化:为验证码等组件缓存过滤掉线程前缀的设置
+
+
+
+jboot v3.16.6 2022-10-25:
+修复:HttpUtil.download 出现 NPE 的问题
+
+
+
+jboot v3.16.5 2022-10-23:
+修复:Controller 使用返回值渲染并使用 cacheable 注解对其缓存时出错的问题
+修复:ObjectUtil.convert() 在某些极端情况下错误的问题
+
+
+
+jboot v3.16.4 2022-10-19:
+优化:doNotAlloVisitRedirect 错别字
+优化:修改 CacheUtil.setCurrentPrefix() 方法为 setThreadCacheNamePrefix
+新增:添加 JbootCache.addThreadCacheNamePrefixIngore() 方法
+
+
+
+jboot v3.16.3 2022-10-17:
+新增:SqlBuilder.escapeOrderBySql 用于过滤 Order By 参数
+新增:InterceptorBuilder.Util.isChildClassOf 方法
+新增:JbootController.getParaToBigInteger(index) 方法
+优化:RequestUtil.getIpAddress() 对 127.0.0.1 的处理
+优化:JbootResourceLoader 的日志输出和目标路径的优化
+
+
+
+jboot v3.16.2 2022-10-07:
+新增:ObjectUtil.obtainNotNull 方法
+新增:StrUtil.obtainNotBlank 方法
+优化:升级 jfinal-undertow 到最新版本并移除 JbootApplication 的 URL 打印功功能。
+
+
+
+jboot v3.16.1:
+新增:APP 在启动的时候,输出 Local URL,方便通过控制台启动浏览器
+新增:FileUtil.ensuresParentExists() 方法
+新增:DAO.deleteAll() 方法
+修复:Controller 通过 return 渲染 Json 数据时,控制台不输出 Render 信息的问题
+修复:JbootController.getFileOnly(name) 返回的数据不正确
+
+
+
+jboot v3.16.0:
+优化:重构 @Cacheable 在 Controller 中的使用,使之更加灵活方便
+优化:FileUtil.unzip 方法,添加可以指定解压缩编码的参数
+优化:升级 JFinal、 jackson-core 等到最新版本
+修复:JbootServiceBase.initDao 在非泛型子类时初始化出错的问题
+修复:Sqlbuilder 在使用别名 + between 时,生成的 SQL 出错的问题
+
+
+
+jboot v3.15.10:
+优化:增强 http 工具类,默认支持携带 cookie 重定向
+优化:升级 Jsoup 到 v1.15.3 最新版本
+修复:对在中文目录下部署时,可能对环境判断错误的问题
+
+
+
+jboot v3.15.9:
+新增:Controller.getFileOnly(name) 和 getFilesOnly(names)
+优化:升级 Jsoup 等相关依赖到最新版本
+修复:当 ActionKey 注解使用 ./ 相对路径时,swagger文档中的 path 会生成包含 ./ 的错误API路径的问题
+
+
+
+jboot v3.15.8:
+新增:FileUtil.delete 方法
+新增:JbootController.getFirstFileOnly() 方法
+优化:JbootController.getFile() 设置为删除方法
+优化:升级 JFinal 到最新版本
+
+
+
+jboot v3.15.7:
+修复:RPC 的 Reference 缓存 key 构建错误导致无法命中缓存的问题
+新增:FileUtil.removeSuffix() 方法
+新增:ModelUtil.keep(List models) 方法
+新增:CollectionUtil.toString() 方法
+新增:ArrayUtil.toString() 方法
+新增:DateUtil 解析 datetime-local 方法
+
+
+
+jboot v3.15.6:
+新增:JbootmqBase 添加自定义线程池的接口方法
+新增:JbootModel.processColumns() 方法,在某些场景下用于对 Columns 进行二次加工
+优化:Redis MQ 的 lpush 对应应该是 rpop 的问题,同时添加 interval 设置方法
+优化:JbootPaginateDirective 添加自动从 scope 获取 Page 对象的方法
+优化:移除 JbootModel 的 getBigInteger 和 getBigDecimal 方法,新版本的 JFinal 已经实现了
+修复:修复 Model.use("datasource").save() 的时候无法正确保存数据的问题
+
+
+
+jboot v3.15.5:
+修复:当开启 nacos 配置中心,有启用带参数内容 ${} 时,出现 StackOverFlow 的问题
+修复:HttpUtil 获取 GBK 网址内容时,出现乱码的问题
+
+
+
+jboot v3.15.4:
+优化:升级适配 Seata 到最新版本 1.5.1,感谢 @扫地猿
+优化:CookieUtil,方便在 Handler 里对 Cookie 进行操作
+文档:新增 dubbo2 升级到 dubbo3 的升级文档,感谢 @自由领主
+
+
+
+jboot v3.15.3:
+新增:代码生成器新增 白名单 配置的支持,感谢 @xujianxie
+优化:升级 FastJson 到最新版本
+
+
+
+jboot v3.15.2:
+修复:DAO 配置了 loadColumns 时又配置了 distinct 时,无法获得正确结果的问题
+优化:优化 Columns.toString() 方法,防止在开发工具 debug 下显示 "null" 的问题
+
+
+
+jboot v3.15.1:
+新增:JbootOutputDirectiveFactory,用于忽略在生产环境下,模板引擎执行的错误输出
+新增:CodeGenHelpler 新增对 infomix 数据库的支持,感谢 @xujianxie
+优化:升级 JFinal 到 v5.0 最新版本,其他相关依赖也升级到新版本
+
+
+
+jboot v3.15.0:
+优化:ValidErrorRender 使用 Ret.fail() 来创建,方便统一定义 message 字段
+优化:InterceptorBuilderManager 默认对所有 Interceptor 进行注入
+优化:Controller 新增 getAttrs() 和 renderToStringWithAttrs() 方法
+修复:Controller 接收 int[] 时,使用 @Size 等注解验证时出错的问题
+修复:Model.dao() 查询在某些情况下出错的问题
+修复:注解 @Size(max=xxx) 拦截错误的问题
+
+
+
+jboot v3.14.9:
+修复:在某些极端场景下,StrUtil.escapeHtml 无法通过 unEscapeHtml 还原的问题
+优化:删除某些无用的方法和类
+
+
+
+jboot v3.14.8:
+修复:DataSourceConfigManager 里的数据源在 APP 启动成功后会被清空的问题
+修复:配置 redis GlobalKeyPrefix,CacheUtil.getKeys(cacheName) 无法正确获取 keys 的问题
+
+
+
+jboot v3.14.7:
+修复:Jboot AppListener 启动时的错误信息,无法正确输出的问题
+
+
+
+jboot v3.14.6:
+修复:JbootLockCounter 和 JbootLocalLock 不可用的问题
+优化:删除一些无用的类或者空实现的类
+
+
+
+jboot v3.14.5:
+新增:JsonUtil.getList 和 JsonUtil.getSet 方法
+新增:RSAUtil 非对称加解密工具类
+新增:DESUtil 对称加解密工具类
+
+
+
+jboot v3.14.4:
+新增:生产环境忽略模板指令渲染错误的功能,保证其他内容正常渲染
+新增:模板错误渲染器 TemplateErrorRender,用于追加模板指令错误内容
+新增:是否开启 Controller Action 缓存的开关,方便在不同的场景下进行开启或者关闭
+新增:JbootEventManager 可以设置自己的线程池
+优化:ValidUtil 中ValidatorFactory初始化一次,供Validator、MessageInterpolator复用,感谢 @wtusrss
+优化:升级 JFinal/JFinal-Undertow 等到最新版本
+修复:HttpUtil 在 POST 的时候,添加了参数又设置 body 内容时,参数失效的问题
+文档:修改文档错别字
+
+
+
+jboot v3.14.3:
+优化:JbootModel.findByColumn 当传入 null 值 value 时,直接返回 null
+优化:Model 保存和之前版本一致的行为,只允许绑定到一个数据源
+优化:当 Model 配置指定数据源,但数据源不存在的时候,给出更为明确的错误提示
+优化:Rabbitmq 添加自由开启队列和广播的开关,默认全部开启
+优化:重构 CORS 模块,使之代码可以用户模块里进行 "复用"
+修复: HttpUtil 在 put 请求时,某些情况下无法正常提交数据的问题
+
+
+
+jboot v3.14.2:
+新增:Informix 数据库方言,感谢 @xujianxie 同学
+新增:JbootModel 新增 findByIdWithoutCache() 方法
+新增:新增 JbootModel 的 CPI 类,用于开放保护方法
+新增:Nacos 远程配置中心添加多实例的配置支持
+新增:网关拦截器 GatewayConfig.interceptors 添加 SPI 名称配置的支持
+新增:JsonUtil.java 方便解析 Json 数据
+优化:ValidUtil.java,支持在非自定义消息的时候输出拦截的列名
+优化:ClassScanner.java 默认关闭 Class 扫描信息在控制台输出
+优化:修改缓存拦截器的默认权重为 100,当前情况下拦截器在最后执行
+
+
+
+jboot v3.14.1:
+新增:注解 @Table(datasource="xxx") 新增对数据源的配置支持
+新增:添加 jboo.app.listenerPackage 配置支持,用于只扫描哪些包的 listener
+优化:完善 JbootJson 更多的代码注释
+优化:优化 JbootConfigManager.java 代码,对添加 key 进行 trim()
+优化:进一步完善 jboot.properties 文件存放位置的探测
+优化:单元测试移除 mockHandler
+优化:DAO.findFirstByColumn(),当传入 null 应抛出错误,防止查询出错误的数据
+优化:rabbitmq.queueDeclareDurable 的配置默认设置为 false
+优化:对 QuietlyUtil 的方法进行重命名优化
+优化:ValidErrorRender 输出的 message 不带有 fieldName 信息
+优化:升级 Jfinal 等依赖到最新版本
+修复:@Cacheput() 注解在 Controller 上无效的问题
+
+
+
+jboot v3.14.0:
+新增:@Cacheable() 等系列缓存注解对 controller 的支持
+优化:优化 fastjson 序列化的功能,使用 config 而非 features
+优化:重命名注解 @TxEnable() 修改为 @Transactional(),并为 @Transactional() 添加更多的配置功能
+优化:为 RabbitMQ 添加更多的自定义配置
+优化:自动检测是否依赖 jfinal-wexin 并自动配置 JbootAccessTokenCache
+优化:删除 JacksonSerializer 等无用的代码文件
+优化:优化配置文件的的自动探测功能,防止在某些新手未编译直接运行找不到配置文件的问题
+优化:分布式任务注解 @EnableDistributedRunnable,并添加自定义的 redisKey 和 key 持有时间配置功能。
+文档:完善关于 Controller 和 数据库的相关文档
+
+
+
+jboot v3.13.8:
+修复:Junit 代码覆盖率测试可能出现多次启动的问题
+修复:JbootCron4jPlugin 停止后未移除已经停止任务的问题
+
+
+
+jboot v3.13.7:
+新增:MQ 新增 stopListening() 方法,可用于定制化关闭 MQ
+修复: Controller 返回值有大写可能不正确的问题
+
+
+
+jboot v3.13.6:
+优化:强化 Controller 返回值,自动匹配相应的 render
+优化:优化 JbootHttpImpl 和 JbootHttpResponse 代码,添加必要的日志输出
+优化:更新 Copyright
+
+
+
+jboot v3.13.5:
+新增:CookieUtil 添加 "defaultPath" 和 "defaultDomain" 的配置
+新增:HttpUtil 添加 http 代理的配置支持
+新增:Columns.addToFirst() 方法
+新增:JbootDirectiveBase.getParaToString()
+优化:RequestUtil.java
+优化:升级 JFinal 到最新版本
+
+
+
+jboot v3.13.4:
+新增:AttachmentManager 新增 getFile(path,localFirst) 方法
+优化:升级相关依赖到最新版本
+
+
+
+jboot v3.13.3:
+新增:配置文件可以指定自己的文件名和路径的支持
+优化:JbootmqBase,使之代码更加简洁
+优化:Mysql 驱动,默认优先使用 MySql8 驱动
+优化:升级相关依赖到最新版本
+
+
+
+jboot v3.13.2:
+新增:AOP 新增 javassist 的实现,方便在 jdk17 去掉 cglib
+修复:在配置前缀的场景下,修复 redis 缓存 removeAll() 无法正确移除数据的问题
+修复:SharedEnumObject 在 jdk17 下无法正常运行的问题
+修复:Fastjson 对某些数据无法正确序列化的问题,升级 fastjson 到最新版本
+
+
+
+jboot v3.13.1:
+新增:JbootDb 新增支持 Columns 的 findFirst 方法
+新增:QuietlyUtil.java 工具类
+新增:ReflectUtil.java 的若干方法,反射调用更加简单
+优化:Redis 缓存,存入 null 时,直接对 key 进行移除
+优化:MockHttpServletRequest 直接实现 HttpServletRequest 接口,而非 HttpServletRequestWrapper 继承
+优化:@TestConfig() 注解新增 printScannerInfo 参数,默认为 false
+优化:MockMvcResult.java 提高性能
+优化:优化网关代码,使代码更加清晰
+修复:移除 JbootModelConfig.idCacheCachePrefix 配置,否则会造成重复配置的情况
+
+
+
+jboot v3.13.0:
+新增:Jbootmq 的监听器(Listener)新增 MessageContext 参数,支持更多的消息配置
+新增:MockMvc 新增 upload 方法,用于对上传文件的测试
+优化:404 403 401 等错误输出 info 信息,而不是 warn
+优化:开放更多的 JbootMqImpl 方法
+优化:优化 Http 工具类,上传文件的时候添加 ContentType 信息
+
+
+
+jboot v3.12.5:
+新增:IdCache 默认的缓存前缀配置
+优化:AOP 缓存添加默认时间为 1 个小时
+优化:为 @Cacheable() 等注解添的key构建加更多的数据类型支持,支持集合和数组
+优化:升级相关依赖到最新版本
+
+
+
+jboot v3.12.4:
+新增:@FilterBy() 注解,用于对某些参数进行内容过滤
+
+
+
+jboot v3.12.2:
+新增:是否处理 404 页面的配置支持,方便与其他 web 框架整合
+新增:JbootAppListener 新增 onConstantConfigBefore() 方法
+优化:AttachmentManager.java,使之 getFile 不返回 null
+优化:JsonManager.me().setDefaultDatePattern 的默认配置
+
+
+
+jboot v3.12.1:
+优化:当 cglib 创建对象出错时,错误信息不明确的问题
+优化:Cache、MQ、Redis 的 Manager 在某些情况下可能出现 NPE 的问题
+优化:升级相关依赖到最新版本
+
+
+
+jboot v3.12.0:
+新增:SPI 新增直接配置类名的功能
+新增:MQ 新增支持多个实例配置的支持
+新增:Redis 新增配置多个实例的支持
+新增:Cache 缓存新增配置多个实例的支持
+新增:限流组件新增 IP 限流功能
+新增:限流组件新增 IP 白名单功能
+优化:Rokect MQ 新增更多的配置支持
+优化:移动 AOP 的默认配置到 "jboot.aop.cache"
+优化:"jboot.model.idCacheType" 配置名称修改为 "jboot.model.idCacheName"
+优化:"jboot.web.session.cacheType" 配置名称修改为 "jboot.web.session.useCacheName"
+优化:重命名 JbootConfigUtil 为 ConfigUtil
+文档:修改文档错误已经添加部分文档
+
+
+
+jboot v3.11.4:
+新增:新增 DAO.distinct() 方法,用于对内容进行去重
+新增:MockMvc 新增 holdCookie 配置,用于在不同的测试用例里,保持 cookie 的功能
+优化:保证 Model 和 Record 的 getBigInteger 的行为一致
+优化:移除 codegen 下的 PathKit 类,使用 CodeGenHelpler 代替
+
+
+
+jboot v3.11.3:
+新增:重写 Model 的 getBigInteger,防止转换异常
+新增:代码生成器 生成的 Model,如果字段是 BigInteger ,自动调用 getBigInteger 方法的功能
+优化:升级相关依赖到最新版本
+
+
+
+jboot v3.11.2:
+新增:Columns.containsName() 方法
+新增:SqlDebugger 新增对 Sql 输出到日志的实现方案,只要只有接口需要自己实现
+优化:捕获 ScheduledThreadPoolExecutor 所有业务异常,防止其意外终止调度
+优化:PaginateDirectiveBase,当其有数据的时候,显示分页内容
+优化:ClassScanner,新增某些排除对象
+优化:删除 CacheableInterceptor 一些不必要的 debug 信息
+
+
+
+jboot v3.11.1:
+新增:SqlDebugPrinter 新增是否打印 SQL 的独立开关
+新增:新增更多的 JbootApplication.createServer(...) 方法
+优化:升级JFinal、Jedis、Naocs、Dubbo 等到最新版本
+修复:AttachmentManager.use(name) 无法使用的问题
+
+
+
+jboot v3.11.0:
+新增:实验性新增 Restful 支持,默认关闭,可以通过 "jboot.web.pathVariableEnable=true" 开启,感谢 @没牙的小朋友
+新增:shiro 配置添加 filter 参数,自定义shiro相关的filter,感谢 @orangeJ
+优化:在shiro拦截器初始化时处理对应方法的注解,取消对路由、actionKey的倚赖,感谢 @orangeJ
+优化:修改 JbootActionMapping 拼写错误
+优化:AttachmentManager 新增多实例的支持
+优化:优化缓存前缀配置的方法名,否则可能会造成误解
+优化:同时修改 "jboot.web.escapeParas"配置为 "jboot.web.escapeParasEnable"
+修复:修正现有swagger实现中,会忽略ActionKey注解中的URL的问题,感谢 @没牙的小朋友
+
+
+
+jboot v3.10.8:
+新增:JbootCache 新增通过前缀了使用 "多实例" 的支持
+新增:Model、Db 动态设置默认数据源的支持
+新增:DatasourceConfig 新增表前缀配置的支持
+修复:由于 RPC 添加 Reference 缓存后,造成 Controller 多个 Service 无法注入的问题
+
+
+
+jboot v3.10.7:
+新增:JbootHttpRequest 添加 sslContext 的配置,方便自定义 ssl
+新增:JbootConfigManager 添加 setBootProperties 方法,方便用于添加启动配置
+新增:工具类 ReflectUtil 新增 searchFieldList() 方法
+新增:添加 TypeConverterFunc,用于处理前端传入枚举内容
+优化:JbootShiroInvokeListener,在 onInvokeBefore() 方法添加返回值 AuthorizeResult,更加方便整合 Jwt sso 等
+优化:升级 jedis/jsoup/jfinal/fastjson 等到最新版本
+
+
+
+jboot v3.10.6:
+修复:ClassScanner 添加排除的前缀时,如果有大写字母不生效的问题
+修复:当使用 @Api(collect={}) 时,子 Controller 路径错误的问题
+
+
+
+jboot v3.10.5:
+新增:@EnableLimit() 注解新增通过 redis 实现集群的限流的功能,感谢 @orangeJ
+新增:为 JbootRedis 增加 eval 方法,用于执行 lua 脚本,感谢 @orangeJ
+新增:redis 缓存增加 globalKeyPrefix 配置,解决多个应用共用一个 redis 实例时的 key 冲突问题,感谢 @orangeJ
+修复:JbootResourceLoader 在 Maven 二级目录下启动,无法扫描上级资源文件的问题
+修复:Sentinel 文档错误的问题
+
+
+
+jboot v3.10.4:
+优化:升级 JFinal-Weixin 到最新版本
+优化:添加 JbootActionReporter.colorRenderEnable 的配置
+修复:通过在 JFinal Routes 配置的 Controller,APIDoc 无法正常生成的问题
+
+
+
+jboot v3.10.3:
+新增:门户网关 Gateway 新增基于 Nacos 的自动服务发现的功能
+新增:JbootHttpImpl 新增默认的 Content-Type 配置
+新增:JbootHttpRequest 新增 "instanceFollowRedirects" 配置
+优化:允许 void 的 Controller 方法生成 retRemarks
+优化:添加 ApiOper.containerClass 配置,用于对 void 类型的 Controller 设置返回值
+优化:添加 ApiDocConfig.defaultContainerClass 配置,用于配置默认的 void Controller 返回值
+优化:对 HttpRequest 进行优化,保证 headers 和 paras 的顺序
+优化:删除 JbootGatewayHandler 默认添加的必要,修改为默认不添加此 Handler
+修复:@ApiPara.require() 在方法上不起作用的问题
+修复:Jboot 升级 JFinal 到最新版本后,代码生成器的路径错误的问题
+示例:新增 Gateway 通过 Nacos 自动发现的 Demo 示例
+示例:新增 WebSocket 的 Demo 示例
+
+
+
+jboot v3.10.2:
+新增:注解 @ApiPara() 增加 require 参数的配置,感谢 @lijiahong
+优化:Json 增加递归深度配置,修复多层级数据返回不完全问题,感谢 @lijiahong
+优化:升级 JFinal、jackson、Dubbo 等到最新版本
+修复:JbootPaginateDirective 在默认情况下无法获取 page 的问题
+修复:Redis 连接有密码时,Redis 集群时未设置 maxAttempts 连接权限不上问题,感谢 @lijiahong
+
+
+
+jboot v3.10.1:
+新增:ApiDoc 新增 allInOne 模式,方便把所有 api 生成到一个文档里
+新增:@Api() 注解新增 orderNo 的配置
+新增:可以通过 api-remarks.json 和 api-mock.json 为文档配置 json 输出
+新增 "ApiJsonGenerator",用于生成通过数据库生成 api-remarks.json 和 api-mock.json 文件
+新增:ApiDoc 文档新增自定义排序 Comparator 配置的支持
+新增:ApiDoc 新增自定义 ApiMockBuilder 的支持,用于构建任意 Model 的 Mock 数据
+新增:"@ApiResp" 注解,用于对 JFinal 通过 render 而非返回值的形式的支持
+新增:ApiDocument 等信息添加序列化的支持
+
+
+
+jboot v3.10.0:
+新增:apidoc 模块
+优化:JbootCaptchaCache 在某些情况下可能出现 NPE 的问题
+优化:升级 JFinal 到 v4.9.14 最新版本
+
+
+
+jboot v3.9.20:
+优化:由于 JFinal 紧急发布新版本,因此同步到 JFinal 最新版本
+修复:因为 ehcache 依赖默认并没有引入,将 session 的默认存储改为 caffeine。感谢 @orangeJ
+修复:@RequestMapping() @Path() 无法配置动态参数的问题
+
+
+
+jboot v3.9.19:
+新增:@MockClass() 注解,用于多 AOP 的 Class 进行 Mock
+新增:MockMvc 新增 requestStartListener 和 requestFinishedListener 的支持
+新增:@TestConfig.devMode 和 @TestConfig.launchArgs 配置的支持
+新增:MockMvcResult.getContentAsJSONObject() 和 assertJson() 方法
+新增:MockHttpServletRequest 新增 addQueryParameter() 方法
+新增:@DefaultValue() 注解,用与对参数配置默认值
+新增:"jboot.sentinel.reqeustTargetPrefix" 配置的支持,用于配置 sentinel 对某些 url 进行拦截
+优化:优化控制台 action 的日志输出的性能
+优化:升级 JFinal 到最新版本
+优化:通过 MockMvc 进行单元测试的时候,若 Controller 发生 404 或者 500 等错误的时候,则不通过 junit 测试
+文档:添加验证器错误自定义渲染的相关文档
+文档:同步 junit 测试文档
+文档:修复 Sentinel 的里错误的配置文档
+
+
+
+jboot v3.9.18:
+新增:新增 @MockMethod 注解,方便对 AOP 方法进行 Mock
+新增:@TestConfig(autoMockInterface=false) 配置,方便对接口进行 Mock 操作
+修复:Motan RPC 框架的 protocol 配置不生效的问题
+修复:ide 配置错误时给出的 JFinal 配置帮助文档网址错误
+
+
+jboot v3.9.17:
+修复:JbootActionReporter 可能出现 NotFoundException 的问题
+修复:JbootActionReporter 可能出现 NPE 的问题
+修复:阿里云商业 MQ Aliyunmq 配置错误的问题,感谢 @不器
+文档:修改某些描述错误的文档
+
+
+
+jboot v3.9.16:
+修复:Junit 测试对于个别 ServletRequest 方法没有 mock 到而出错的问题
+修复:当 Jboot 有上层 session 时(比如使用 shiro),修改 Controller session 无法同步上层 session 的问题
+
+
+
+jboot v3.9.15:
+新增:新增单元测试的辅助类的支持
+新增:ActionReporter 新增 render 信息的输出功能
+新增:工具类 ReflectUtil.java
+优化:调整默认的JbootShiroInvokeListener实现,保存被拦截的请求便于后续跳转使用,感谢 @没牙的小朋友
+修复:jboot.properties配置文件中 jboot.shiro.ini 配置未生效的问题,感谢 @没牙的小朋友
+修复:在某些情况下 PathKit.getWebRootPath 得到错误结果的问题
+
+
+
+jboot v3.9.14:
+新增:为 undertow 新增默认的 content-type,解决 mp4 等视频不能播放的问题
+新增:ValidErrorRender,方便用户自定义 "数据验证" 错误的渲染器
+修复:当 Interceptor 被 cglib 代理时,无法正确输出其日志的问题
+
+
+
+jboot v3.9.13:
+新增:新增配置 "jboot.app.listener",用于配置可以执行的 appListener
+新增:新增配置 "jboot.json.skipModelAttrs" 和 "jboot.json.skipBeanGetters" 配置
+优化:JbootGatewayHandler 默认添加在系统里,方便进行动态路由
+优化:JbootGatewayHealthChecker 的代码
+优化:升级 JFinal 等依赖到最新版本
+优化:对 ClickHouse 高级版本 驱动 进行适配
+修复:代码生成器在某些情况下输出的 html 不是 utf8 编码的问题
+修复:WeightUtil 判断错误的 bug
+
+
+
+jboot v3.9.12:
+新增:devModel 可以动态配置,方便在某些场景下切换 devMode
+修复:在某些极端场景下,PathKit.getWebRootPath() 可能出错的问题
+修复:当查询的 Page 为 null 时,分页的总页数数据错误的问题
+
+
+
+jboot v3.9.11:
+新增:注解 @ActionKey() 支持 ./ 相对路径的配置
+新增:门户网关拦截器新增对 Header 的配置
+新增:JbootActionReporter 的开关配置,而不是由 devMode 决定
+优化:门户网关的健康检查代码抽离为独立的类 JbootGatewayHealthChecker
+优化:门户网关 NoneHealthUrlErrorRender 重构为 GatewayErrorRender,支持更多的错误渲染
+优化:门户网关可以通过拦截器自定义前端渲染功能
+优化:简化 RPC 的默认配置
+优化:优化 JWT 的代码逻辑
+优化:升级 JFinal、Seata 等到最新版本
+优化:优化 ConfigUtil 的参数解析方法
+优化:优化 FastJsonSerializer 的逻辑代码
+文档:优化序列化的相关文档
+
+
+
+jboot v3.9.10:
+新增:Dubbo Method 的 oninvoke/onreturn/onthrow 配置的支持
+新增:不同的 @RPCIject 持有不同 RPC 对象的支持
+优化:当 provider 未启动,但是开启 check 后无法注入,但提示信息不明确的问题
+优化:升级 jackson、dubbo 等到最新版本
+修复:ConfigUtil 在读取 Object 时可能出错的问题
+修复:RPCUtil 在配置 default 有时可能无效的问题
+修复:Dubbo 版本升级后,不再支持对 consumer 的 protocol 配置
+修复:Dubbo 升级到 2.7.10 后,若不配置直连协议,启动出错的问题
+
+
+
+jboot v3.9.9:
+新增:@TxEnable() 注解的支持,方便在 Service 进行事务处理
+优化:优化 JsonBodyParseInterceptor.java 代码
+优化:升级 JFinal、FastJson 等到最新版本
+修复:泛型的 Controller 无法覆盖子类 Action 的问题 [#I3FG0B](https://gitee.com/JbootProjects/jboot/issues/I3FG0B)
+修复:使用 @JsonBody 注解,同时使用 泛型 的 Controller 在 openJdk 下可能出错的问题
+修复:paginateByColumns 传入复杂的 Order By 可能产生错误的问题
+文档:修复 Dubbo 文档 url 路径错误的问题
+
+
+
+jboot v3.9.8:
+新增:@GetMapping() 和 @PostMapping() 的支持
+新增:@JsonBody() 注解支持 LocalDate 和 LocalDateTime
+新增:@JsonBody() 注解支持在 Class 定义泛型的功能
+优化:控制输出的 Action 时间执行时间,包含了模板引擎的渲染时间。
+优化:Jwt 拦截器构建器的代码
+优化:门户网关的 Http 代理代码
+优化:升级 Nacos 等依赖到最新版本
+
+
+
+jboot v3.9.7:
+新增:@Lazy 懒加载注入的功能
+新增:JbootController.getOrginalRequest() 方法,用于获取进过 Xss 处理后的原始 Request
+新增:JbootDirectiveBase.getParaToBigDecimal() 的系列方法
+新增:Interceptors.addIfNotExist() 方法,用于对某些注解进行单次添加
+新增:JbootController.getParaToBigDecimal() 和 getParaToBigInteger() 方法
+优化:重命名 JFinalEnumObject 为 SharedEnumObject
+优化:Validator 验证错误的时候,错误信息给出错误的相关字段
+修复:通过 @JFinalShareEnum 添加枚举,在某些极端情况下无法调用枚举静态方法的问题
+
+
+
+jboot v3.9.6:
+新增:门户网关动态配置拦截器的功能(之前只能通过配置文件进行配置)
+新增:门户网关动态配置负载均衡策略的功能(之前只能通过配置文件进行配置)
+新增:columns 新增 groupBy() 和 having() 的方法,方便构建 group by 的 SQL
+新增:新增 jboot.web.escapeParas 配置,方便全局对 xss 进行防护
+优化:升级 JFinal、HikariCP、metrics、Shiro 等到最新版本
+修复:使用 @Bean 注解,然后 Jboot.getBean() 通过 Bean Name 获取不到对象的问题
+修复:Redis 缓存的 removeAll() 和 getKeys() 在某些情况返回数据不正确的问题
+
+
+
+jboot v3.9.5:
+新增:CacheInterceptorBuilder.Util 工具类,用于对拦截器的判断
+优化:重构 @Configuration 对 @Bean 的初始化工作
+优化:JbootCglibCallback 对没有拦截器的方法的调用
+修复:在热加载的情况下,JbootCoreConfig 无法转换为 JFinalConfig 的问题
+
+
+
+jboot v3.9.4:
+新增:@JfinalSharedEnum 注解的支持,方便把枚举添加到模板引擎里使用
+新增:@PostConstruct 注解的支持,方便 Bean 在被创建的时候进行初始化
+优化:ApplicationUtil.runInFatjar 的判断
+文档:更新 MVC、MQ、Gateway 等文档
+
+
+
+jboot v3.9.3:
+新增:门户网关没有健康网关时可以自定义渲染器的功能
+优化:Gateway 门户网关的性能
+优化:GatewayInterceptpor 拦截器可以获得当前的代理目标 URL 地址功能
+修复:@DecimalMax 和 @DecimalMin 在某些极端情况下可能验证错误的问题
+文档:更新 MQ、Sentinel、Gateway、Validator 等文档
+
+
+
+jboot v3.9.2:
+新增:门户网关 Gateway 的健康检查功能
+新增:JbootModel.closeIdCacheTemporary() 通过这个方法可以一次关闭ID缓存
+新增:Controller 新增 getJwtParaToInt 等系列方法
+新增:JbootHttpRequest 新增 readBody 配置,用于在某些情况下不读取 http body
+优化:StrUtil.queryStringToMap 方法
+优化:优化门户网关的若干方法,添加必要的注释
+优化:升级 JFinal、Sentinel、Seata 等到最新版本
+修复:修复 clickhouse 在某些情况下,分页可能出错的问题
+
+
+
+jboot v3.9.1:
+修复:JbootModel.loadCache 在某些情况下出错的问题
+修复:分布式配置启用 Apollo 后可能出现空指针的问题
+
+
+
+jboot v3.9.0:
+新增:Clickhouse 数据源的配置和支持
+新增:Rocketmq 作为 mq 底层通信的支持
+新增:ValidUtil.setValidator() 方法用于自定义自己的 Validator
+新增:ValidateInterceptorUtil.setValidExceptionRetBuilder 用于渲染自定义验证错误信息
+新增:jboot.model.idCacheByCopyEnable 配置用于开启 id 缓存的时候是否返回 copy 的对象
+优化:优化 Rabbitmq 和 阿里云商业 MQ 的支持
+优化:修改 Validate 返回数据的 errorCode 的值为 400
+优化:重命名 ControllerUtil 为 JbootShiroUtil
+优化:JbootController 去获取 Jwt 内容的时候,不配置 secret 只会发出警告而不是抛出异常
+优化:重置 Rabbitmq 的 broadcastQueuePrefix 配置为 broadcastChannelPrefix
+
+
+
+jboot v3.8.1:
+优化:增强 Sentinel 与其控制面板设置的能力,增加数据源配置,并可以通过阿里云 AHAS 进行完全控制
+优化:升级 Undertow 的 devMode 默认值为 false,防止在 devMode 的情况下,不同的 classloader 导致获取不到数据查询实例的问题
+修复:修复分布式 Seata 的 bug 、升级 Seata 到最新版本并添加相关测试代码,感谢 @菜农
+修复:修复 Rabbitmq 在某些情况下出现 bug 的问题,并添加相关测试代码
+
+
+
+jboot v3.8.0:
+新增:代码生成器生成 Service 的前缀和后缀配置
+优化:升级 Jfinal、Jfinal-undertow、Jfinal-weixin 等到最新版本
+优化:AopFactory 优化对 @Configration 的空注解的构建
+优化:完全重构 Metrics,添加对 Prometheus 的输出
+
+
+
+jboot v3.7.8:
+修复:修复 v3.7.7 的 Aop.get(Interface.class) 在某些情况下无法获取服务的问题
+
+
+
+jboot v3.7.7:
+优化:优化 @Configuration 注解初始化流程
+优化:AOP 对接口或者抽象类进行注入,但有找不到其实现类的时候可能出现 methodNoFund 的错误
+修复:通过 @Bean(name=xxx) 注解去定义 Service 时在某些情况下可能无法正常获取的问题
+
+
+
+jboot v3.7.6:
+新增:新增默认的 DriverClassNames,当用户不配置的时候使用默认的进行配置
+新增:JbootController.getJsonBody() 的方法
+优化:默认添加 validation-api 验证框架依赖
+优化:DateUtil 当前端传入 null 值的时候有适合的返回值
+优化:jwt 拦截器提高性能,并新增清空 jwt 数据的方法
+修复:JbootController.toBigDecimal 当已 N 开头时解析不对的问题
+
+
+
+jboot v3.7.5:
+新增:支持更多的验证注解,比如 Digits DecimalMax DecimalMin Positive Negative 等
+新增:支持在 Service 任何被注入的方法里进行验证
+新增:JbootApplication 运行下 fatjar 的时候,支持在同级目录下读取 jboot.properties 文件
+
+
+
+jboot v3.7.4:
+新增:NotNull、NotBlank、NotEmpty、Valid、Pattern、Min、Max、Size、Email 等验证注解
+新增:RequestUtil.isJsonContentType() 方法
+优化:NotEmpty、Regex 等验证返回更加当前请求类型返回 json 内容
+优化:JbootErrorRender,当发生错误的时候,对 ajax 请求返回 json 内容
+优化:移除注解 Weight 的默认值
+修复:JbootController.getRawObject 在某些情况下可能出现异常的问题
+修复:使用 Autoload 注解,同时配置 Weight 不生效的问题
+
+
+
+jboot v3.7.3:
+新增:JbootController 新增 getRawObject(TypeDef) 方法,可以获得指定泛型数据
+优化:JsonBodyParseInterceptor 的解析效率
+优化:JbootController 的 getRawObject 的效率
+修复:当最后一个节点是数组而去获取指定 index 的 Object 时,返回 null 的问题
+
+
+
+jboot v3.7.2:
+新增:@JsonBody 新增新的 JsonKey 语法支持
+新增:JbootController 新增传入 JsonKey 获取数据
+
+
+
+jboot v3.7.1:
+新增:DateUtil 新增 getStartOfDay() 和 getEndOfDay() 两个方法
+新增:@JsonBody 支持多 Date 类型的注入
+优化:DateUtil 支持更多的自动 parse 方法
+修复:@JsonBody 对原始数据类型的注入为 null 是不正确的问题
+
+
+
+jboot v3.7.0:
+优化:提高在 Controller 有多个 @JsonBody 参数时 JsonBodyParseInterceptor 解析性能
+修复:JsonBodyParseInterceptor 在解析原始数据参数出错的问题
+修复:JbootJedisImpl 在某些情况下配置 database 无效的问题
+
+
+
+jboot v3.6.9:
+新增:@JsonBody 新增对 set 的支持
+优化:升级 JFinal、Undertow、Jackson 等到最新版本
+
+
+
+jboot v3.6.8:
+新增:Controller 参数新增 @JsonBody 的支持
+新增:Http 工具模块添加更多的配置,方便在 fatjar 模式下能配置 https 相关证书
+新增:ErrorRender 自动判断前端是否需要 json 渲染,当请求头是 application/json 的时候自动渲染错误的 json
+
+
+
+jboot v3.6.7:
+优化:JbootSimpleApplication,使之代码更加简洁
+优化:优化缓存拦截器对方法的 key 进行构建,提高性能
+优化:默认为 JbootAccessTokenCache 添加 2 个小时的缓存时间
+优化:LocalAttachmentContainer,默认保存文件的时候,对文件进行相同文件验证
+优化:JwtManager 对 Jwt 解析出错或没有 Jwt 数据时,返回常量 map
+
+
+
+jboot v3.6.6:
+新增:AttachmentManager 新增保存文件的若干方法
+修复:DateUtil 某些方法不正确的问题
+
+
+
+jboot v3.6.5:
+新增:控制台信息打印新增 RawData 的内容打印,方便开发调试
+优化:优化通过 JbootSimpleApplication 启动 RPC 服务时控制台的输出信息
+修复:Jboot 在某些特殊场景下对 fatjar 运行模式判断不正确的 bug
+修复:通过 JbootSimpleApplication 启动时,Pathkit 无法正确获取路径的问题
+
+
+
+jboot v3.6.4:
+修复:DateUtil 的 bug,和新增若干方法
+优化:StrUtil.mapToQueryString 的方法
+
+
+
+jboot v3.6.3:
+新增:DateUtil 工具类,方便对日期进行计算或转换
+新增:StrUtil.queryStringToMap 和 StrUtil.mapToQueryString
+优化:JbootCaptchaCache 进行缓存 Captcha 时,配置过期时间
+修复:CORSInterceptorBuilder 构建跨域注解拦截器时,注解在类上获取错误
+
+
+
+jboot v3.6.2:
+新增:控制台新增 Jwt 参数内容的打印,方便调试开发
+新增:jboot.app.resourceLoaderEnable 配置,在 dev 模块下可以关闭 resourceLoader
+优化:重命名 JbootInvocation 为 JbootActionInvocation
+
+
+
+jboot v3.6.1:
+新增:JbootCaptchaCache 方便在分布式的场景下进行验证码验证
+新增:JbootTokenCache 方便在分布式下进行 token 验证
+新增:JbootLock,方便在本地模式下分布式模型下进行锁机制编写
+优化:RequestMapping 注解添加空字符串 "" 配置的支持
+优化:移动 JbootAccessTokenCache 的包名
+优化:重构 JbootCounter,使其在单体模型下和分布式场景下有一致的特征
+
+
+
+jboot v3.6.0:
+新增:ClassUtil.hasClass 方法,用于判断一些第三方组件依赖
+新增:Model 新增 @JsonIgnore 注解,用于配置某些不输出的字段
+新增:Model 新增 FastJson 的注解 @JsonField 的配置支持
+新增:新增对 JFinal 最新版本 @Path 的配置支持
+新增:DAO 查询的时候,支持通过 loadColumns() 方法配置其查询的列
+修复:Columns组装sql工具类in()、notIn()方法新增安全模式检查参数是否为空,解决安全模式下,List不为null,但没有元素时跳过安全检查。感谢 @liuenxin
+优化:Columns.in() 和 Columns.notIn 修改参数 List 为 Collection
+优化:升级 JFinal、Fastjson、Nacos 等到最新版本
+优化:为 Json 的相关代码创建独立包
+优化:重构 JbootJson 使之代码更加简洁
+优化:重命名 SqlDebugger.debug 为 SqlDebugger.run,删除其不必要的方法
+优化:优化 AopCache,使之在缓存数据错误时,自动刷新缓存信息
+文档:更新 json 的相关文档
+
+
+
+jboot v3.5.9:
+新增:StrUtil.obtainDefault 方法用于替代 obtainDefaultIfBlank 方法
+新增:jboot.web.cookieMaxAge 的配置支持,用于配置默认的 cookie 保存时间
+优化:提升配置参数 ${xxx} 解析性能
+优化:JbootController 的代码注释错误
+优化:ClassScanner
+文档:优化 motan rpc 的构建文档,增加必要的说明,感谢 @zcoder
+文档:新增 Nacos 分布式配置下的一些文档
+
+
+
+jboot v3.5.8:
+新增:StrUti.obtainDefault 替代 obtainDefaultIfBlank
+优化:JbootAopFactory,新增 @Bean 注解对非接口类的支持
+优化:ApolloConfigManager 对 Apollo 配置的管理
+修复:修复 Redis 缓存 removeAll 在某些情况下错误的问题
+
+
+
+jboot v3.5.7:
+新增:JbootRedis 新增 scan 方法
+优化:JbootRedisCache 的 getKeys removaAll 等方法使用 scan 去读取 keys
+优化:重构 Jwt 模块,使之代码更加简洁
+
+
+
+jboot v3.5.6:
+新增:Jboot 通过 columns 查询新增设置主表别名的支持
+优化:重构 SqlDebugger,控制台实时输出 Sql 的执行时间,方便对 Sql 进行优化
+优化:SwaggerController,兼容请求地址结尾没斜杠json加载失败问题,感谢 @xiaoyu512
+优化:优化 JbootRpcBase onStart 方法,减少子类复写时调用不必要的方法
+修复:高并发下,第一次去获取 RPC 服务时,可能为 null 的问题,感谢 @huangzekai_1
+文档:优化 json 相关文档注释不明确的问题
+
+
+
+jboot v3.5.5:
+新增:JbootCaptchaRender 渲染,可以自定义动态码的内容
+新增:AopCache 新增 setAopCache 方法,方便通过代码配置 Aop 缓存
+优化:重构 JWT 模块,使之在调整 Jwt 拦截器顺序时也可以正常工作
+优化:移除 JbootAopInvocation,使代码更加简洁
+优化:Utils.putDataToCache 方法,使其更加便于阅读
+优化:优化 Redis 订阅模块,使其在应用关闭时主动断开连接
+优化:升级 fastjson、druid 等到最新版本
+
+
+
+jboot v3.5.4:
+优化:使用 InterceptorBuilder 重构 Sentinel 模块,使代码更加简洁
+优化:使用 InterceptorBuilder 重构 Seata Tcc 模块,使代码更加简洁
+优化:Interceptors 可以通过 Class 直接添加,而无需添加具体的实例
+优化:更新 JFinal、JFinal-undertow 等到最新版本
+优化:移动 MixedByteArrayOutputStream 所在的包目录
+修复:解决CGLIB代理下获取不到 @Weight 注解问题,感谢 @huangzekai_1
+
+
+
+jboot v3.5.3:
+优化:CaffeineCacheBuilder 构建器可能根据名称进行构建
+优化:提高 CDN 渲染的构建性能
+修复: Caffeine Cache 在某些极端情况下可能存在多份 Cache 的问题,感谢波总 @JFinal
+修复:当 Render 发生错误时,html 页面无法再次渲染 500 错误页面或者异常信息的问题
+
+
+
+jboot v3.5.2:
+优化:InterceptorBuilderManager,添加移除 Builder 等方法及其相关测试
+优化:ClassUtil,完善 singleton 等方法
+优化:Jboot 缓存默认类型 由 ehcache 修改为 caffeine
+优化:优化启动输出内容 和 sql 打印内容
+优化:JbootRender,当不启用 CDN 的时候进一步提升性能
+修复:AttachmentManager,当分布式文件不存在时,访问文件出现空指针的问题
+
+
+
+jboot v3.5.1:
+优化:JbootRedisCacheImpl buildKey() 方法
+优化:JbootJson 并新增更多的配置
+优化:InterceptorBuilderManager 的方法名并添加更多可配置的方法
+
+
+
+jboot v3.5.0:
+新增:InterceptorBuilder 组件,方便对 Controller 或者 AOP 对象的拦截器进行构建
+优化:移除 FixedInterceptor 组件,其可以通过 InterceptorBuilder 进行替代
+优化:新增 Controller 方法是否被正常执行的 log
+优化:优化 AttachmentManager 对分布式文件的渲染流程
+
+
+
+jboot v3.4.3:
+新增:指令 @JFinalDirective 新增 override 配置,用于覆盖系统已经内置的指令
+优化:优化 AttachmentManager,使之更加方便的上传 获取文件
+
+
+
+jboot v3.4.2:
+修复:当使用 Apollo 配置中心时,在某些场景下会导致 devMode 判断不正确的问题
+优化:重构 PaginateDirectiveBase,使之显示更加 "人性化" 和支持更多的功能配置
+
+
+
+jboot v3.4.1:
+修复:在 fatjar 模式下,通过 --jboot.app.mode 启动参数配置应用模式无效的问题
+修复:在 fatjar 模式下,不同位置的 java -jar 启动可能会导致 ClassScanner 两次扫描 jar 拖慢启动速度的问题
+修复:paginate 分页方法无法正确输出 sql 的问题
+
+
+
+jboot v3.4.0:
+新增:门户网关 Gateway 新增自定义负载均衡策略的支持
+新增:AttachmentContainer 组件,方便自定义把附件上传到其他第三方任何平台
+新增:全新的文档地址 和 Jboot 官网
+修复:数据源 Datasource 的 validationQuery 属性配置不生效的问题
+修复:SqlDebugPrinter 对参数为 Boolean 数据输出的格式不正确的问题
+优化:升级 Sentinal、Metrics、JFinal-Weixin 等到最新版本
+优化:删除 JbootActionReporter 一些不必要的方法
+优化:删除一些不必要的 注释信息
+优化:修改 JbootPaginateDirective 自动去获取当前的 page 信息,而不需配置
+优化:移除 JbootHttpImpl 默认的 content-type 配置
+优化:重构 Metrics 读取的相关处理,在 Metrics 未配置的时候,没必要添加相关 Handler,提升性能
+
+
+
+jboot v3.3.5:
+修复:通过门户网关下载文件 或者 渲染图片可能出现乱码的问题
+优化:重构 Http 工具类里的 HttpRequest 里的某些方法
+优化:增强 JbootActionReporter 功能,使之可以输出未被执行的拦截器 以及 Controller 的执行时间
+
+
+
+jboot v3.3.4:
+修复:当 Action 定义在父类后,JbootActionReporter 获取不到 Method 而出错的问题
+优化:升级 nacos 和 Apollo 客户端到最新版本
+优化:JbootHttpImpl Post 提交数据资源可能存在不正常关闭的情况
+
+
+
+jboot v3.3.3:
+修复:JbootActionReporter 对拦截器 Interceptor 输出的行号不正确的问题
+
+
+
+jboot v3.3.2:
+新增:新增 JbootActionReporter 用于代替 JFinal 的 ActionReporter,更精准的地位方法
+优化:重命名 ParaValidateInterceptor 为 ValidateInterceptor
+修复:@EmptyValidate @RegexValidate 的 message 在 ajax 上提示不正确的问题
+
+
+
+jboot v3.3.1:
+新增:Seata tcc 的支持
+新增:RegexValidate 注解对 Controller 进行验证的支持
+优化:ClassScanner 排除对 protobuf 扫描
+优化:JbootUndertowConfig 排除对 Jboot.java 的依赖
+优化:升级 JFinal、FastJson 等到最新版本
+
+
+
+jboot v3.3.0:
+新增:ObjectUtil 工具类,用于对 Object 进行对比等操作
+新增:JbootModel.useFirst() 方法,更加方便在读写分离的场景下进行使用
+新增:JsonTimestampPattern 配置,方便控制 json 的日期输出
+新增:JbootServiceBase.findListByIds() 方法
+新增:Columns 新增 safeCreate 方法
+优化:重构 JbootConfigChangeListener ,方便监听远程配置的每个值的变化
+优化:JbootServiceBase.syncModels 由传入 List 修改为 Collection
+优化:升级 Seata 到最新版本 1.3.0
+优化:升级远程配置 nacos、Apollo 到最新版本
+优化:删除 JbootrpcManager 里的某些无用的逻辑判断
+修复:当有多数据源时,join 会参数数据不正确的问题
+修复:当主键是 String 类型时,DAO.findListByIds() 没有返回数据的问题
+修复:CookieUtil 当设置的时间为 0 或者 -1 的时候,时间验证不正确的问题
+
+
+
+jboot v3.2.9:
+修复:Columns.in() 对传入 int[] long[] short[] 不能正确支持的问题
+
+
+
+jboot v3.2.8:
+新增:Columns.toWherePartSql() 方便构建 sql
+新增:ObjectFunc.java 方便通过 Java8 lambda 调用
+新增:JbootServiceJoiner.joinMany 方法,方便进行一对多的查询
+新增:JbootServiceJoiner.joinManyByTable 方法,方便通过第三映射表进行多对多查询
+新增:JbootServiceJoiner.syncModels 方法,用过同步数据到数据库
+修复:Columns.likeAppendPrecent 传入空数据时结果出错的问题
+修复:Seata 对 dubbo 的 SPI 过滤器文件名错误的问题
+优化:当 jwt 解析出错时,输出错误的日志信息
+优化:重命名 joinById() 为 joinByValue(),因为传入的值不一定只是 id
+文档:新增 一多一、一对多、多对多查询的相关文档
+
+
+
+jboot v3.2.7:
+优化:修改 JbootModel.buildIdCacheKey 为 protected 修饰,方便在某些情况下进行重写。
+优化:Columns 在 safeMode 模式下,当传入 null 值时,直接抛出空指针异常,更加方便开发调试。
+
+
+
+jboot v3.2.6:
+修复:Columns 调用 unUseSafeMode 后可能存在问题的 bug
+
+
+
+jboot v3.2.5:
+新增:Columns 查询添加 safeMode ,使用 safeMode 当传入的查询值 null 值的 sql 参数时,不对齐忽略直接返回空数据。
+优化:升级 fastjson、jackson Json 等到最新版本
+修复:JbootAppListener 无法进行正确注入的问题
+
+
+
+jboot v3.2.4:
+优化:ClassScanner 和 JbootJson
+新增:为 JbootJson 新增 camelCaseToLowerCaseAnyway 配置,默认为 false
+
+
+
+jboot v3.2.3:
+优化:升级 fastsjon 到最新版本 1.2.71
+修复:Model 的 getter 方法无法输出 json 的问题
+
+
+
+jboot v3.2.2:
+优化:当项目启动的时候,优先初始化中央配置,以防止 undertow 端口等无法在中央仓库配置的问题
+优化:新增 Columns.in(list) 和 Columns.notIn(list) 方法
+优化:新增 Columns.append() 方法用于追加一个新的 columns
+优化:Json输出默认使用驼峰的字段风格
+优化:升级 Nacos、Fastjson 等到最新版本
+修复:通过 Columns 查询 count,当有 left join 时会出现数量不正确的问题
+修复:当 RPC 注解有 ${} 时,无法读取配置内容的问题
+文档:修改配置文件里的示例带有双引号的错误配置
+文档:优化 PRC 的相关文档
+
+
+
+jboot v3.2.1:
+优化:升级 JFinal-Undertow、JFinal-Weixin 到最新版本
+优化:完善支持更多关于 druid 的数据源配置
+优化:当未配置任何第三方日志组件的时候,自动使用 JDK 日志进行输出
+优化:添加 JbootRedirectRender,防止 nginx -> jboot 跳转时的错误问题
+优化:移除 @ValidatePara 注解 和 UrlParaValidate 验证拦截器
+优化:移除 Jboot 的 @EnableCORS 注解,使用 JFinal 自带的来替代
+优化:修改某些变量命名不直观的问题
+优化:默认情况下完全禁用 Fastjson 的 autoType 功能
+文档:添加 dubbo rpc 下的 restful 配置文档
+
+
+
+jboot v3.2.0:
+新增:JbootController 新增 getParaToBigInteger()、getParaToBigDecimal() 等方法
+新增:门户网关新增 hasException() 方法,用于判断目标地址是否可以正常访问
+优化:升级 JFinal、jackson、HikariCP、Dubbo 等相关依赖到最新版本
+文档:配置相关文档添加动态配置的相关描述
+文档:数据库配置相关添加多数据源的相关描述
+
+
+
+jboot v3.1.9:
+新增:Jboot.configValue(key,default) 方法
+新增:JbootAppListener.onStartFinish()方法,用于不同的 Module 在 onStart 进行操作。
+修复:当在配置文件中配置的内容为 '{' 字符开头的时候会出现 ArrayIndexOutOfBoundsException 异常的问题
+优化:升级 Seata 到 v1.2.0 最新版本
+优化:移除 rpc 服务暴露成功后的日志输出
+优化:统一 JbootCron4jPlugin.addTask() 中的 deamon 参数默认为 false
+优化:重命名 app/config/Utils 为 ConfigUtil
+优化:重构 Restructure AnnotationUtil.get(),以便支持更加灵活的参数配置
+
+
+
+jboot v3.1.8:
+新增:Gateway 新增动态注册路 和 移除由配置的功能
+新增:Gateway 被 Sentinel 拦截后自定义返回 Json 的功能
+新增:Gateway 新增对多个 host 的支持,默认走随机匹配的负载均衡机制
+新增:新增 Dubbo 下的对 consumer/provider/register/protocol 的默认配置的支持
+优化:修改 JbootServiceBase 的 DAO 属性类型为 JbootModel
+优化:优化 JbootrpcBase 和 Prop 的一些输出日志
+优化:重命名 PRCUtils 为 PRCUtil,保持 Jboot 工具类统一
+优化:重命名 JbootRpcApplication 为 JbootSimpleApplication
+优化:重构 DubboUtil 代码,使之更加简洁
+优化:使用JsonKit来替换 FastJson 的直接使用,解决开发场景中使用其它json库的情况下,不会出错,感谢 @ yangyao
+优化:升级 Guava 等相关的 Maven 依赖到最新版本
+修复:修复 ClassScanner 扫描不到 Shiro 指令的问题
+修复:JbootGatewayManager 默认名字配置错误的问题
+修复:PRCUtil 无法添加某些注解注解属性导致@RPCInject某些参数无效的问题
+修复:启用分布式配置 Nacos 时,在 Nacos 配置的中文会出现乱码的问题
+修复:使用 Motan RPC 框架时,出现引用错误的问题
+修复:Dubbo 下的一些 consumer 配置失效的问题
+修复:修复 fatjar 打包的时候,需要单独配置 BaseTemplatePath,否则出错的问题
+文档:优化 gateway 的相关文档
+文档:优化 config配置 的相关文档
+文档:优化 rpc 的相关文档
+
+
+
+jboot v3.1.7:
+新增:JWT 可以通过 request para 传入数据的支持
+修复:复写 ActionHandler.getAction 可能无效的问题
+优化:排除 Jboot 内部不必要的 Class 扫描
+
+
+
+jboot v3.1.6:
+新增:JbootReturnValueRender,用于可以在 Action 里进行返回值渲染
+新增:ResponseEntity,可以直接在 Action 返回其进行渲染
+优化:对 JbootGatewayConfig 网关配置进行优化,用户配置了错误的 uri 时会给出提示
+优化:升级 Dubbo、Motan、Sentinel、Nacos 等到最新版本
+优化:ClassScanner 添加一些常用的排除 jar 和 class
+优化:更多关于 Nacos 分布式配置中心的配置支持
+修复:RPCUtils.copyFields() 无法正确复制配置内容的问题
+修复:JbootRpcApplication 无法正确启动插件和拦截器的问题
+文档:修改 AOP 文档的内容错误问题
+文档:完善分布式配置中心对 Nacos 以及 Apollo 支持的相关文档
+
+
+
+jboot v3.1.5:
+新增:新增 Motan RPC 的 export 和 host 的相关配置以及test代码
+新增:JbootJson 支持驼峰式 JsonKey 输出,感谢Gitee的 @herowjun
+新增:新增 Controller 对返回值自动渲染的功能
+新增:JbootRPCConfig 新增默认 version 和 group 配置的支持
+优化:ClassScanner 添加无需扫码的 jar 排除,速度更快
+修复:ClassScanner 在 Windows 平台下可能存在重复扫码而拖慢启动速度的问题
+修复:门户网关 GatewayHttpProxy 在 POST 时的某些情况下会出现无法正确代理的问题
+文档:完善 RPC 配置的相关文档
+文档:完善 Gateway 配置的相关文档
+文档:添加 fatjar 打包的相关文档
+文档:完善 fatjar 部署运行的相关文档
+
+
+
+jboot v3.1.4:
+优化:重构 ClassScanner ,提高在 fatjar 模式的扫描性能
+优化:重构 ConfigManager ,以便更好的支持 fatjar 模式下的配置文件读取
+优化:重构 JbootCoreConfig,方便在 fatjar 下能够准确读取 html 等资源文件
+优化:重构 JbootCoreConfig,把 JFinal-Weixin 设置非必须依赖
+
+
+
+jboot v3.1.3:
+新增:新增对 JDK11+ 的支持
+
+
+
+jboot v3.1.1:
+新增:Gateway 网关自定义拦截器的支持,方便通过网关进行鉴权等操作
+新增:Gateway 网关新增错误重试次数的配置,方便网关重试配置
+新增:jboot.properties 配置里的 value 值添加 ${para} 参数的支持
+新增:undertow 端口配置,添加 -1 的支持,通过 -1 配置可用的随机端口
+新增:JbootRpcApplication,方便用于只启动 RPC Service 服务,用于给消费者提供服务
+修复:修复 @RPCInject 在拦截器使用的时候出错的问题
+修复:RPC 工具类 Utils.appendAnnotation 无法正确给 int 、boolean 参数赋值的问题
+优化:当不对 Dubbo 的 qos 配置的时候,设置为默认关闭,方便单机情况下进行开发调试
+文档:更新 Gateway 网关的相关文档
+
+
+
+jboot v3.1.0:
+新增:新增网关的支持,路由规则支持 host、path、query等 三种模式,同时支持基于 Sentinel 的限流配置
+新增:新增对 Dubbo 多协议、多注册中心等的支持
+新增:新增对注解 @Configuration 的支持,可以通过其构建 name 实例
+新增:JbootAopInterceptor 可以动态的添加或者移除拦截器
+新增:配置文件可以配置 Map、Set、List 和 数组的支持
+新增:JbootCache 新增可以获取素有 Names 的功能,方便对缓存进行运维
+新增:JbootCache 新增可以对分布式缓存进行刷新,方便对缓存进行运维
+新增:jboot-system.properties 的支持,用于替代启动参数的 -D,使用第三方组件的时候更加方便
+修复:修复 JbootHttpImpl 无法上传文件的bug
+修复:修复 JbootHttpImpl 无法正确获取 gzip 压缩内容的问题
+优化:重构 RPC 的模块,使之更加简单清晰
+优化:升级 fastsjon 等相关依赖到最新版本,修改某些错误单词拼写的方法名等
+文档:完善 AOP 的相关文档
+文档:完善 Sentinel 限流的文档
+文档:完善 RPC 的相关文档
+文档:新增 网关配置使用的相关文档
+
+
+
+jboot v3.0.5:
+修复:SqlBuilder 在某些极端情况下生成 SQL 的一些问题
+优化:移除 Jboot 的一些过时的方法
+
+
+
+jboot v3.0.4:
+新增:Jboot MQ 新增 Local 类型
+新增:sqlPart 支持参数设置
+优化:修改 Columns.string() 为 Columns.sqlPart()
+优化:JbootModelConfig 的 primarykeyValueGenerator 和 Filter 支持通过 api 动态配置
+优化:StrUtil.isNumeric、isDecimal、isEmail 等方法
+优化:完善 JbootCounter 功能
+修复:当 Columns.group() 内容为空的时候,构建了错误Sql的问题
+修复:Columns.isNullIf 条件判断错误的问题
+
+
+
+jboot v3.0.3:
+新增:Cache 模块新增 refresh() 的方法,在分布式缓存在某些极端情况下出现不同步的时候进行刷新。
+新增:对 Columns 优化,新增 gourpIf()/stringIf()/isNullIf() 等方法
+新增:新增 PrimaryKeyValueGenerator 对 Model 的主键值生成策略配置
+新增:新增 JbootModelFilter 对 Model 的过滤器策略配置,可以用于在 save 或者 update 的时候防止 Model 存在 xss 等问题
+优化:对 MQ 进行优化,当不配置的时候可能返回一个错误的 MQ 对象的问题
+优化:升级 JFinal-Wexin、Fastjson、Fastxml 等依赖到最新版本
+优化:JbootDirectiveBase 当传入空值的时候明确返回 null
+修复:当 Columns 查询的时候,使用 group 会出现 value 内容缺失的问题
+
+
+
+jboot v3.0.2:
+新增:StrUtil 新增 splitToSetByComma() 方法
+新增:StrUtil 新增 escapeModel() 和 escapeMap() 方法
+优化:StrUti.isDecimal() 方法,防止在某些极端情况下出现判断不正确的问题
+优化:对 pom.xml 进行优化,排除非必要的依赖
+优化:重构 Sentinel 模块,修改为非必须依赖
+
+
+
jboot v3.0.1:
修复:紧急修复 v3.0.0 必须依赖 nacos-client 的问题
diff --git a/doc/.vuepress/config.js b/doc/.vuepress/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..44c3e1413ffb9b1256045b08c754c5c316a5dbeb
--- /dev/null
+++ b/doc/.vuepress/config.js
@@ -0,0 +1,157 @@
+//参考:
+// https://github.com/vuejs/vuepress/blob/master/packages/docs/docs/.vuepress/config.js
+// https://vuepress-theme-reco.recoluan.com/views/1.x/
+module.exports = {
+ title: 'Jboot 官方网站',
+ description: 'Jboot 一个开源的分布式、商业级微服务框架。',
+ // base:'/docs/',
+
+ theme: 'vuepress-theme-reco',
+ themeConfig: {
+ //腾讯 404 公益配置
+ noFoundPageByTencent: false,
+
+ mode: 'light', // 默认 auto,auto 跟随系统,dark 暗色模式,light 亮色模式
+ modePicker: false, // 默认 true,false 不显示模式调节按钮,true 则显示
+ subSidebar: 'auto',//在所有页面中启用自动生成子侧边栏,原 sidebar 仍然兼容
+ // author
+ author: 'jboot',
+
+ // if your docs are in a different repo from your main project:
+ docsRepo: 'yangfuhai/jboot',
+ // if your docs are in a specific branch (defaults to 'master'):
+ docsBranch: 'master',
+ // if your docs are not at the root of the repo:
+ docsDir: 'doc',
+ // defaults to false, set to true to enable
+ editLinks: true,
+ // custom text for edit link. Defaults to "Edit this page"
+ editLinkText: '编辑此页面',
+
+
+ lastUpdated: '更新时间', // string | boolean
+
+ nav: [
+ {text: '首页', link: '/'},
+ {text: 'Jboot文档', link: '/docs/'},
+ {text: 'JbootAdmin', link: '/jbootadmin/'},
+ {text: '提问', link: 'https://gitee.com/JbootProjects/jboot/issues'},
+ {text: 'JPress', link: 'http://www.jpress.io'},
+ {
+ text: '源码下载', items: [
+ {text: 'Gitee', link: 'https://gitee.com/JbootProjects/jboot'},
+ {text: 'Github', link: 'https://github.com/yangfuhai/jboot'}
+ ]
+ },
+ ],
+
+ sidebar: {
+ '/docs/': [{
+ title: '认识 Jboot',
+ collapsable: false,
+ children: [
+ {title: 'Jboot 简介', path: '/docs/'},
+ {title: '快速开始', path: '/docs/start'}
+ ],
+ },
+
+ {
+ title: '开发文档',
+ collapsable: false,
+ children: [
+ {title: '安装', path: '/docs/install'},
+ {title: '配置', path: '/docs/config'},
+ {title: 'JFinalConfig', path: '/docs/jfinalConfig'},
+ {title: 'MVC', path: '/docs/mvc'},
+ {title: 'Validator', path: '/docs/validator'},
+ {title: 'WebSocket', path: '/docs/websocket'},
+ {title: 'Json', path: '/docs/json'},
+ {title: 'Jwt', path: '/docs/jwt'},
+ {title: 'AOP', path: '/docs/aop'},
+ {title: '数据库', path: '/docs/db'},
+ {title: '缓存', path: '/docs/cache'},
+ {title: 'Redis', path: '/docs/redis'},
+ {title: 'RPC 调用', path: '/docs/rpc'},
+ {title: 'MQ 消息队列', path: '/docs/mq'},
+ {title: 'Gateway 网关', path: '/docs/gateway'},
+ {title: '任务调度', path: '/docs/schedule'},
+ {title: 'Jboot限流', path: '/docs/limit'},
+ {title: 'Sentinel限流', path: '/docs/sentinel'},
+ {title: '分布式附件管理', path: '/docs/attachment'},
+ {title: '监控', path: '/docs/metrics'},
+ {title: '事件机制', path: '/docs/event'},
+ {title: '序列化', path: '/docs/serialize'},
+ {title: 'SPI扩展', path: '/docs/spi'},
+ {title: '单元测试', path: '/docs/junit'},
+ {title: '代码生成器', path: '/docs/codegen'},
+ {title: 'API 文档生成', path: '/docs/apidoc'},
+ {title: '项目构建', path: '/docs/build'},
+ {title: '项目部署', path: '/docs/deploy'},
+ {title: 'Docker', path: '/docs/docker'},
+ {title: '热加载', path: '/docs/hotload'},
+ {title: 'Shiro', path: '/docs/shiro'},
+ {title: 'Swagger', path: '/docs/swagger'},
+ ],
+ },
+
+ {
+ title: '性能',
+ collapsable: false,
+ children: [
+ {title: '性能测试', path: '/docs/benchmark'},
+ ],
+ }
+ ],
+
+
+ '/jbootadmin/': [{
+ title: '认识 JbootAdmin',
+ collapsable: false,
+ children: [
+ {title: '简介', path: '/jbootadmin/'},
+ {title: '功能介绍', path: '/jbootadmin/feature'},
+ {title: '我要购买', path: '/jbootadmin/buy'}
+ ],
+ },
+ {
+ title: '开发文档',
+ collapsable: false,
+ children: [
+ {title: '开始', path: '/jbootadmin/start'},
+ {title: '数据库设计', path: '/jbootadmin/db'},
+ {title: '后台菜单', path: '/jbootadmin/menu'},
+ {title: '权限设计', path: '/jbootadmin/permission'},
+ {title: '前端组件', path: '/jbootadmin/front'},
+ {title: '安全防护', path: '/jbootadmin/safety_precautions'},
+ ],
+ },
+ {
+ title: '运维和部署',
+ collapsable: false,
+ children: [
+ {title: '部署', path: '/jbootadmin/deploy'},
+ {title: 'CDN配置', path: '/jbootadmin/cdn'},
+ {title: '文件同步', path: '/jbootadmin/attachment'},
+ {title: '配置中心', path: '/jbootadmin/config'},
+ {title: '门户网关', path: '/jbootadmin/gateway'},
+ {title: '服务器管理', path: '/jbootadmin/server'},
+ ],
+ },
+ ]
+ },
+ sidebarDepth: 1
+ },
+
+ head: [
+ ['link', {rel: 'icon', href: '/logo.png'}],
+ ['script', {}, `
+ var _hmt = _hmt || [];
+ (function() {
+ var hm = document.createElement("script");
+ hm.src = "https://hm.baidu.com/hm.js?d6b9b94a6fafaa41c63920e1af80bcaf";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(hm, s);
+ })();
+ `]
+ ]
+}
diff --git a/doc/.vuepress/styles/index.styl b/doc/.vuepress/styles/index.styl
new file mode 100644
index 0000000000000000000000000000000000000000..0dd1d9126ce94ed060db99e9366b53b2c1e0d6ed
--- /dev/null
+++ b/doc/.vuepress/styles/index.styl
@@ -0,0 +1,28 @@
+#app .page-title, #app .theme-reco-content.content__default, #app footer, #app .page-nav, #app .comments-wrapper {
+ max-width: 960px !important;
+ // padding: 1.5rem !important;
+}
+
+.page {
+ /* 处理文本溢出 */
+ word-wrap: break-word !important;
+ /* 允许再单词内换行 */
+ word-break: break-all;
+ padding-top: 4rem !important;
+}
+
+.page .page-title {
+ // 调整标题容器边距
+ padding: 0rem 1.5rem !important;
+}
+
+ //, #app .theme-reco-content.content__default > p:nth-child(2)
+#app .theme-reco-content.content__default > h1{
+ // 处理文档的第一行标题
+ display: none !important;
+}
+
+.home-center.content__default{
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
\ No newline at end of file
diff --git a/doc/CNAME b/doc/CNAME
new file mode 100644
index 0000000000000000000000000000000000000000..cf681f252a279283e9947d930b2fa6e0ac105f32
--- /dev/null
+++ b/doc/CNAME
@@ -0,0 +1 @@
+jboot.io
\ No newline at end of file
diff --git a/doc/deploy.sh b/doc/deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..69b97ca042d48a107f05d817ea10db222f489eea
--- /dev/null
+++ b/doc/deploy.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env sh
+
+# abort on errors
+set -e
+
+# build
+yarn build
+
+
+
+cp CNAME .vuepress/dist
+
+# navigate into the build output directory
+cd .vuepress/dist
+
+
+# if you are deploying to a custom domain
+# echo 'www.example.com' > CNAME
+
+git init
+git add -A
+git commit -m 'deploy'
+
+# if you are deploying to https://.github.io
+git push -f git@github.com:yangfuhai/yangfuhai.github.io.git master
+
+git push -f git@github.com:yangfuhai/yangfuhai.github.io.git master:gh-pages
+
+# if you are deploying to https://.github.io/
+# git push -f git@github.com:/.git master:gh-pages
+
+cd -
\ No newline at end of file
diff --git a/doc/deploy_to_alioss.sh b/doc/deploy_to_alioss.sh
new file mode 100755
index 0000000000000000000000000000000000000000..aac1c6f428d86d44cd2433211c04dc46ae9c69c3
--- /dev/null
+++ b/doc/deploy_to_alioss.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env sh
+
+# abort on errors
+set -e
+
+# build
+yarn build
+
+ossutil rm oss://jboot-doc-site/ -rf
+ossutil cp -rf .vuepress/dist oss://jboot-doc-site/
\ No newline at end of file
diff --git a/doc/deploy_to_gitee.sh b/doc/deploy_to_gitee.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b7edd49c798045ee45eced3c6281bb00889c120d
--- /dev/null
+++ b/doc/deploy_to_gitee.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env sh
+
+# abort on errors
+set -e
+
+# build
+yarn build
+
+
+
+cp CNAME .vuepress/dist
+
+# navigate into the build output directory
+cd .vuepress/dist
+
+
+# if you are deploying to a custom domain
+# echo 'www.example.com' > CNAME
+
+git init
+git add -A
+git commit -m 'deploy'
+
+# need add config base:'/docs/',
+# if you are deploying to https://.github.io
+git push -f https://gitee.com/JbootProjects/docs.git master
+
+# if you are deploying to https://.github.io/
+# git push -f git@github.com:/.git master:gh-pages
+
+cd -
\ No newline at end of file
diff --git a/doc/docs/aop.md b/doc/docs/aop.md
new file mode 100644
index 0000000000000000000000000000000000000000..9bb9fc20f933cd330fa3f58b13434d71483f512a
--- /dev/null
+++ b/doc/docs/aop.md
@@ -0,0 +1,446 @@
+# 面向切面编程
+
+## 目录
+
+- JFinal AOP
+- JBoot AOP
+- @Inject
+- @RPCInject
+- @Bean
+- @BeanExclude
+- @Configuration
+- @ConfigValue
+- @StaticConstruct
+- @PostConstruct
+- InterceptorBuilder
+
+## JFinal AOP
+
+参考文档:https://jfinal.com/doc/4-6
+
+## JBoot AOP
+
+JBoot AOP 在 JFinal AOP 的基础上,新增了我们在分布式下常用的功能,同时借鉴了 Spring AOP 的一些特征,对 JFinal AOP 做了增强,但是又没有 Spring AOP 体系的复杂度。
+
+## @Inject
+
+我们可以通过 @Inject 对任何 Bean 的属性进行注入,例如 Controller
+
+```java
+@RequestMapping("/helloworld")
+public class MyController extends Controller{
+
+ @Inject
+ private UserService userService;
+
+ public void index(){
+ renderJson(userService.findAll());
+ }
+}
+```
+
+在以上的例子中,在默认情况下,JFinal AOP 会去实例化一个 UserService 实例,并注入到 MyController 的 userService 中,因此需要注意的是:UserService 必须是一个可以被实例化的类,**不能是**抽象类或者接口(Interface)。
+
+如果说,UserService 是一个接口,它有实现类比如 `UserServiceImpl.class`,JFinal 提供了另一种方案,代码如下:
+
+```java
+@RequestMapping("/helloworld")
+public class MyController extends Controller{
+
+ @Inject(UserServiceImpl.class)
+ private UserService userService;
+
+ public void index(){
+ renderJson(userService.findAll());
+ }
+}
+```
+
+## @Bean
+在以上的例子中,我们认为 `@Inject(UserServiceImpl.class)` 这可能不是最好的方案,因此,JBoot 提供了可以通过注解 `@Bean` 给 `UserServiceImpl.class` 添加在类上,这样 Jboot 在启动的时候,会自动扫描到 `UserServiceImpl.class` ,并通过 JbootAopFactory 把 UserService 和 UserServiceImpl 添加上关联关系。
+
+代码如下:
+
+Controller:
+
+```java
+@RequestMapping("/helloworld")
+public class MyController extends Controller{
+
+ @Inject
+ private UserService userService;
+
+ public void index(){
+ renderJson(userService.findAll());
+ }
+}
+```
+
+UserService:
+
+```java
+public interface UserService{
+ List findAll();
+}
+```
+
+
+UserServiceImpl:
+
+```java
+@Bean
+public class UserServiceImpl implements UserService{
+ public List findAll(){
+ //do sth
+ }
+}
+```
+
+当一个接口有多个实现类,或者当在系统中存在多个实例对象,比如有两份 Cache 对象,一份可能是 Redis Server1,一份可能是 Redis Server2,或者有两份数据源 DataSource 等,在这种情况下,我们注入的时候就需要确定注入那个实例。
+
+这时候,我们就需要用到 `@Bean(name= "myName")` 去给不同的子类去添加注释。
+
+例如:
+
+
+```java
+@RequestMapping("/helloworld")
+public class MyController extends Controller{
+
+ @Inject
+ @Bean(name="userServieImpl1") //注入名称为 userServieImpl1 的实例
+ private UserService userService1;
+
+ @Inject
+ @Bean(name="userServieImpl2") //注入名称为 userServieImpl2 的实例
+ private UserService userService2;
+
+ public void index(){
+ renderJson(userService.findAll());
+ }
+}
+```
+
+UserService:
+
+```java
+public interface UserService{
+ List findAll();
+}
+```
+
+
+UserServiceImpl1:
+
+```java
+@Bean(name="userServiceImpl1")
+public class UserServiceImpl1 implements UserService{
+ public List findAll(){
+ //do sth
+ }
+}
+```
+
+
+UserServiceImpl2:
+
+```java
+@Bean(name="userServiceImpl2")
+public class UserServiceImpl2 implements UserService{
+ public List findAll(){
+ //do sth
+ }
+}
+```
+
+这种情况,只是针对一个接口有多个实现类的情况,那么,如果是一个接口只有一个实现类,但是有多个实例,如何进行注入呢?
+
+参考 @Configuration 。
+
+
+## @BeanExclude
+
+当我们使用 `@Bean` 给某个类添加上注解之后,这个类会做好其实现的所有接口,但是,很多时候我们往往不需要这样做,比如:
+
+```java
+@Bean
+public class UserServiceImpl implements UserService,
+OtherInterface1,OtherInterface2...{
+
+ public List findAll(){
+ //do sth
+ }
+}
+```
+
+在某些场景下,我们可能只希望 UserServiceImpl 和 UserService 做好映射关系,此时,`@BeanExclude` 就派上用场了。
+
+如下代码排除了 UserServiceImpl 和 OtherInterface1,OtherInterface2 的映射关系。
+
+```java
+@Bean
+@BeanExclude({OtherInterface1.cass,OtherInterface2.class})
+public class UserServiceImpl implements UserService,
+OtherInterface1,OtherInterface2...{
+
+ public List findAll(){
+ //do sth
+ }
+}
+```
+
+## @Configuration
+
+
+在 Jboot 中的 `@Configuration` 和 Spring 体系的 `@Configuration` 功能类似。
+
+
+我们可以在一个普通类中添加注解 `@Configuration` , 然后在其方法通过 `@Bean` 去对方法进行添加注解。
+
+例如:
+
+```java
+@Configuration
+public class AppConfiguration {
+
+ @Bean(name = "myCommentServiceFromConfiguration")
+ public CommentService myCommentService1(){
+ CommentService commentService = new CommentServiceImpl();
+ return commentService;
+ }
+
+ @Bean
+ public CommentService myCommentService2(){
+ CommentService commentService = new CommentServiceImpl();
+ return commentService;
+ }
+}
+
+```
+
+这样,在一个 Jboot 应用中,就会存在两份 `CommentService` 他们的名称分别为:myCommentServiceFromConfiguration 和 myCommentService2(当只用了注解 @Bean 但是未添加 name 参数时,name 的值为方法的名称)
+
+这样,我们就可以在 Controller 里,通过 `@Inject` 配合 `@Bean(name = ... )` 进行注入,例如:
+
+
+```java
+@RequestMapping("/aopcache")
+public class AopCacheController extends JbootController {
+
+ @Inject
+ @Bean(name="myCommentService2")
+ private CommentService commentService;
+
+ @Inject
+ @Bean(name = "myCommentServiceFromConfiguration")
+ private CommentService myCommentService;
+
+
+ public void index() {
+ System.out.println("commentService:"+commentService);
+ System.out.println("myCommentService:"+myCommentService);
+ }
+}
+```
+
+
+## @ConfigValue
+
+在 AOP 注入中,可能很多时候我们需要注入的只是一个配置内容,而非一个对象实例,此时,我们就可以使用注解 `@ConfigValue`,例如:
+
+```java
+@RequestMapping("/aop")
+public class AopController extends JbootController {
+
+ @ConfigValue("undertow.host")
+ private String host;
+
+ @ConfigValue("undertow.port")
+ private int port;
+
+ @ConfigValue(value = "undertow.xxx")
+ private int xxx;
+}
+```
+
+此时,配置文件 jboot.properties (包括分布式配置中心) 里配置的 undertow.host 的值自动赋值给 host 属性。其他属性同理。
+
+
+## @StaticConstruct
+
+静态的构造方法。
+
+在某些类中,这个类的创建方式并不是通过 new 的方式进行创建的,或者可能构造函数是私有的,或者这个类可能是一个单例示例的类,比如:
+
+```java
+public class JbootManager {
+
+ private static JbootManager me = new JbootManager();
+
+ public static JbootManager me() {
+ return me;
+ }
+
+ private JbootManager(){
+ //do sth
+ }
+}
+```
+
+我们在其他类注入 JbootManager 的时候,并不希望通过 new JbootManager() 的方式进行创建注入,还是希望通过其方法 `me()` 进行获取。
+
+此时,我们就可以给 JbootManager 类添加 `@StaticConstruct` 注解,例如:
+
+```java
+@StaticConstruct
+public class JbootManager {
+
+ private static JbootManager me = new JbootManager();
+
+ public static JbootManager me() {
+ return me;
+ }
+
+ private JbootManager(){
+ //do sth
+ }
+}
+```
+
+但如果 JbootManager 有多个返回自己对象的静态方法,我们就可以使用 `@StaticConstruct` 的 value 参数来指定。
+
+例如:
+
+```java
+@StaticConstruct("me")
+public class JbootManager {
+
+ private static JbootManager me = new JbootManager();
+
+ public static JbootManager me() {
+ return me;
+ }
+
+ public static JbootManager create(){
+ return new JbootManager();
+ }
+
+ private JbootManager(){
+ //do sth
+ }
+}
+```
+
+## @PostConstruct
+
+`@PostConstruct` 是 Java 自带的注解,用于在对 Java 对象初始化、并进行注入成功之后,进行初始化的工作。
+
+比如:
+
+```java
+@Bean
+public class YourServiceImpl implements YourService{
+
+ @Inject
+ private OtherService1 otherService1;
+
+ @Inject
+ private OtherService2 otherService2;
+
+
+ private Service3 service3;
+
+ public YourServiceImpl(){
+ // 构造方法运行的时候, otherService1 和 otherService2 都为 null,
+ // 因为还没有开始注入
+ }
+
+ @PostConstruct
+ private void initService3() {
+ // 构造函数执行完毕后,Jboot 会自动立即执行此方法
+ // 此时 otherService1 和 otherService2 已经有值了
+
+ otherService1.doSth();
+ otherService2.doSth();
+
+ service3 = createService3(otherService1,otherService2);
+ }
+
+ public void doSomething(){
+ service3.xxx();
+ }
+}
+```
+
+此时,我们就可以通过如下方法使用 YourService 了
+
+```java
+YourService service = Aop.get(YourService.class);
+service.doSomething();
+```
+
+`@PostConstruct` 的特点:
+
+- 必须是 `非静态` 方法
+- 必须是 `无参数` 方法
+- 在同一个类中,只能存在一个方法被 `@PostConstruct` 修饰。
+- 如果子类和父类都存在 `@PostConstruct` 修饰的方法,子类优先执行,父类后执行。
+
+
+
+## InterceptorBuilder
+
+InterceptorBuilder : 拦截器构建器。他能够根据当前 Controller、Service 等的方法信息(比如方法注解、方法名称、方法参数),来对当前方法动态 添加 或者 删除 拦截器,
+在之前 JFinal 的体系里,给某个方法添加拦截器只能通过 @Before({MyInterceptor.class}) 或者 configInterceptor() 配置方法里添加全局拦截器。
+
+这样带来一个不够优雅的地方是,如果我们想给某个方法 ”增强“,只能以添加全局拦截器的方式来做,这样所有方法都会被该拦截器拦截,
+然后再通过其判断进行 ”放行“ 。这样就带来了性能效率的下降,因为在我们整个系统中,有非常多的方法是没有必要走拦截器的。
+
+有了 InterceptorBuilder 之后,我们可以通过 InterceptBuilder 给某个方法来构建其特定的拦截器,而不是全局拦截器,
+这样,极大的提高了性能,同时减少了不必要的调用堆栈。
+
+
+如何使用 InterceptorBuilder 呢?如下代码:
+
+```java
+public class YourAppListener extends JbootAppListenerBase{
+
+ public void onInit() {
+ InterceptorBuilderManager.me().addInterceptorBuilder(new MyInterceptorBuilder());
+ }
+}
+```
+
+或者 `CacheInterceptorBuilder` 添加主键 `@AutoLoad`
+
+
+> 注意:使用注解 `@Autoload` 之后,就不要通过 `InterceptorBuilderManager` 来添加了,`@Autoload` 的作用就是在其启动的时候自动添加进去。
+
+比如 `@Cacheable` 的拦截器构建 CacheInterceptorBuilder 代码如下:
+
+```java
+@AutoLoad //自动把当前的 CacheInterceptorBuilder 添加到 InterceptorBuilderManager 里去。
+public class CacheInterceptorBuilder implements InterceptorBuilder {
+
+ /**
+ * 开始都某个方法进行构建
+ * @param serviceClass 方法所在的类
+ * @param method 方法信息
+ * @param interceptors 该方法已经有的拦截器
+ */
+ @Override
+ public void build(Class> serviceClass, Method method, Interceptors interceptors) {
+
+ Cacheable cacheable = method.getAnnotation(Cacheable.class);
+ if (cacheable != null) {
+ interceptors.add(new CacheableInterceptor());
+
+ // 或者使用如下的方式添加,这样,所有的方法都会被同一个 "实例" 拦截
+ // interceptors.add(CacheableInterceptor.class)
+ }
+
+ }
+}
+```
+
+在 `build()` 方法中的 Interceptors,我们不仅仅可以通过 Interceptors 来添加拦截器,我们还可以通过其来删除拦截器、或者修改拦截器的顺序等等操作。
\ No newline at end of file
diff --git a/doc/docs/apidoc.md b/doc/docs/apidoc.md
new file mode 100644
index 0000000000000000000000000000000000000000..07830aaae7edcfc2b46d7126c2ff9fbc6470e581
--- /dev/null
+++ b/doc/docs/apidoc.md
@@ -0,0 +1,237 @@
+# Jboot API 文档生成
+
+在 Jboot 中内置了 4 个注解,用于生成帮助开发者生成 API 文档。它们分别是
+
+- @Api :给 Controller 配置,一个 Controller 生成一个文档文件。
+- @ApiOper :给 Controller 的方法配置。
+- @ApiPara :给 Controller 的参数进行配置。
+- @ApiResp :给 Controller 的 Render 内容进行配置。
+
+## 基本使用
+
+生成文档的过程,需要自己写一个 `main()` 方法,然后通过 ApiDocManager 来生成,代码如下:
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config = new ApiDocConfig();
+ config.setBasePath("./doc/api");
+
+ ApiDocManager.me().genDocs(config);
+ }
+}
+```
+
+Controller 代码如下:
+
+```java
+@RequestMapping("/api/user")
+@Api("用户相关API")
+public class UserApiController extends ApiControllerBase {
+
+ @Inject
+ private UserService userService;
+
+
+ @ApiOper("用户登录")
+ @ApiResp(field="Jwt",notes="Jwt 的 Token 信息")
+ public Ret login(
+ @ApiPara(value = "登录账户", notes = "可以是邮箱") @NotNull String loginAccount
+ , @ApiPara("登录密码") @NotNull String password) {
+ //....
+ return Ret.ok().set("Jwt","....");
+ }
+
+
+ @ApiOper("用户详情")
+ public Ret detail(@ApiPara("用户ID") @NotNull Long id) {
+ //....
+ }
+
+
+ @ApiOper("更新用户信息")
+ public Ret update(@ApiPara("用户 json 信息") @JsonBody @NotNull User user) {
+ //....
+ }
+}
+```
+
+默认情况下,` ApiDocManager.me().genDocs(config)` 生成的是 Markdown 文档,内容如下:
+
+
+
+## 不同的文档生成在不同的目录
+
+一般情况下,如下的代码会去找到所有带有 `@Api` 注解的 `Controller` 生成文档,并生成在同一个目录:
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config = new ApiDocConfig();
+ config.setBasePath("./doc/api");
+
+ ApiDocManager.me().genDocs(config);
+ }
+}
+```
+
+不同的 `Controller` 生成在不同的目录,代码如下:
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config1 = new ApiDocConfig();
+ config1.setBasePath("./doc/api1");
+ config1.setPackagePrefix("com.xxx.package1");
+
+ ApiDocManager.me().genDocs(config1);
+
+
+
+ ApiDocConfig config2 = new ApiDocConfig();
+ config2.setBasePath("./doc/api2");
+ config2.setPackagePrefix("com.xxx.package2");
+
+ ApiDocManager.me().genDocs(config2);
+ }
+}
+```
+
+## 多个 `Controller` 生成一个文档
+
+`@Api` 注解提供了一个 `collect` 的配置,用于汇总其他 `Controller` 的接口。 例如:
+
+```java
+@RequestMapping("/api/user")
+@Api(value="用户相关API",collect={Controler1.class, Controller2.class})
+public class UserApiController extends ApiControllerBase {
+
+
+ @ApiOper("用户登录")
+ public Ret login(@ApiPara(value = "登录账户", notes = "可以是邮箱") @NotNull String loginAccount
+ , @ApiPara("登录密码") @NotNull String password) {
+ //....
+ }
+
+
+}
+```
+
+`@Api(value="用户相关API",collect={Controler1.class,Controller2.class})` 表示 `UserApiController` 生成
+的文档会把 `Controler1` 和 `Controller2` 的接口也汇总到此文档里来。
+
+> 注意:此时,`Controler1` 和 `Controller2` 不再需要添加 `@Api` 注解。
+
+## 所有的 API 生成在 1 文档里
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config = new ApiDocConfig();
+ config.setBasePath("./doc/api");
+
+ //所有的 api 信息生成在同一个文档里
+ config.setAllInOneEnable(true);
+
+ ApiDocManager.me().genDocs(config);
+ }
+}
+```
+
+
+
+
+## 无参数的 Action 生成 API 文档
+在 Jboot 和 JFinal 中,有很多 Controller 的方法可能不是带有参数的,而是通过 `getPara()` 等方法来获取参数。
+
+```java
+@RequestMapping("/api/user")
+@Api(value="用户相关API")
+public class UserApiController extends ApiControllerBase {
+
+ public Ret login() {
+ String loginName = getPara("loginName");
+ String password = getPara("password");
+
+ //....
+ }
+}
+```
+
+我们可以使用 `@ApiParas` 注解,代码如下:
+
+```java
+import io.jboot.apidoc.annotation.ApiPara;
+import io.jboot.apidoc.annotation.ApiParas;
+
+@RequestMapping("/api/user")
+@Api(value = "用户相关API")
+public class UserApiController extends ApiControllerBase {
+
+
+ @ApiParas({
+ @ApiPara(value="登录名",name="loginName"),
+ @ApiPara(value="密码",name="password"),
+ })
+ public Ret login() {
+ String loginName = getPara("loginName");
+ String password = getPara("password");
+
+ //....
+ }
+}
+```
+
+## 生成 word、html 或更多...
+
+默认情况下,在如下的代码中,`ApiDocManager` 生成的是 Markdown 文档。
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config = new ApiDocConfig();
+ config.setBasePath("./doc/api");
+
+ ApiDocManager.me().genDocs(config);
+ }
+}
+```
+
+
+如果我们需要生成 word、html 等其他文档,需要自定义 `ApiDocRender` ,并配置给 `ApiDocManager`。
+
+```java
+public class MyApiDocRender extends ApiDocRender {
+
+ void render(List apiDocuments, ApiDocConfig config){
+ //自定义渲染,生成 html 或者 word 等等...
+ }
+
+}
+```
+
+配置 `MyApiDocRender` 并开始生成文档。
+
+```java
+public class ApiDocGenerator {
+
+ public static void main(String[] args) {
+
+ ApiDocConfig config = new ApiDocConfig();
+ config.setBasePath("./doc/api");
+
+ ApiDocManager.me().setRender(new MyApiDocRender());
+ ApiDocManager.me().genDocs(config);
+ }
+}
+```
\ No newline at end of file
diff --git a/doc/docs/attachment.md b/doc/docs/attachment.md
new file mode 100644
index 0000000000000000000000000000000000000000..0362bd11418247840a0de51b90fab026c28b1706
--- /dev/null
+++ b/doc/docs/attachment.md
@@ -0,0 +1,304 @@
+# Attachment 附件管理
+
+Jboot 定位是分布式的开发系统,在项目进行分布式部署的时候,用户在上传文件时,我们需要对文件进行分布式同步也是必须的。
+
+在分布式部署的需求下,我们假设我们把我们的应用部署在 A/B/C 三台服务器上,这三台服务器通过 nginx 或者 SLB 等做负载均衡。
+
+此时,也就意味着当用户每次访问我们的应用的时候,可能访问到了 A 服务器,也有可能访问到 B 服务器 或者 C 服务器。
+
+当我们的应用假设有一个图片上传的功能,用户在上传图片的时候,假设上传到了 A 服务器,但是第二次去访问附件的时候,可能访问到了 B 服务器,但是 B 服务器却不存在 A 用户刚刚上传的图片,Jboot 的 AttachmentContainer 就是为了解决这一系列问题而存在的。
+
+在使用 AttachmentContainer 之前,我们先来了解下以下的几个概念。
+
+- AttachmentContainer : 附件容器,就是专门用来存放图片、读取图片和渲染http请求图片的。
+- AttachmentManager : 用来管理 AttachmentContainer 的,一个应用里可以有多个 AttachmentContainer 。比如,阿里云OSS存储的,我们可以来定义一个 AliyunOSSAttachmentContainer; fastDFS 存储我们一样可以来定义一个 FastDFSAttachmentContainer;同时,Attachment 内置了一个默认的 AttachmentContainer,用来存在 ”本地“ 附件的。
+
+在使用 AttachmentContainer 之前,我们需要需要编写自己的一个类,来实现 AttachmentContainer 接口,并添加到 AttachmentManager 里去。
+
+```java
+AliyunOssAttachmentContainer aliyunOss = new AliyunOssAttachmentContainer();
+AttachmentManager.me().addContainer(aliyunOss);
+```
+
+当我们的 Controller 有文件上传的时候,我们需要调用 AttachmentManager 进行保存,AttachmentManager 最终会保持到其所有的容器里去。
+
+例如:
+
+```java
+public void upload() {
+ if (!isMultipartRequest()) {
+ renderError(404);
+ return;
+ }
+
+ UploadFile uploadFile = getFile();
+ if (uploadFile == null) {
+ renderJson(Ret.fail().set("message", "请选择要上传的文件"));
+ return;
+ }
+
+ //通过 AttachmentManager 去保存文件
+ String relativePath = AttachmentManager.me().saveFile(file);
+ file.delete();
+
+ renderJson(Ret.ok().set("success", true).set("src", relativePath));
+}
+```
+
+通过 `AttachmentManager.me().saveFile(file);` 保存文件,AttachmentManager 会保持到所有的容器里。
+
+当需要读取文件的时候,我们也可以通过 AttachmentManger 去读文件。
+
+```java
+File attachment = AttachmentManager.me().getFile(relativePath);
+```
+
+`AttachmentManager.me().getFile(relativePath);` 读取文件的时候,会优先从 默认 的 ”容器“ 去读取,当默认 ”容器“ 不存在该文件的时候,AttachmentManager 会遍历所有的 Container ,直到读到为此。
+
+
+以下是 `AttachmentManager.me().getFile()` 代码的实现逻辑:
+
+```java
+public File getFile(String relativePath) {
+
+ AttachmentContainer defaultContainer = getDefaultContainer();
+
+ //优先从 默认的 container 去获取
+ File file = defaultContainer.getFile(relativePath);
+ if (file != null && file.exists()) {
+ return file;
+ }
+
+ for (Map.Entry entry : containerMap.entrySet()) {
+ AttachmentContainer container = entry.getValue();
+ try {
+ if (container != defaultContainer) {
+ file = container.getFile(relativePath);
+ if (file != null && file.exists()) {
+ return file;
+ }
+ }
+ } catch (Exception ex) {
+ LOG.error("get file error in container :" + container, ex);
+ }
+ }
+ return null;
+}
+```
+
+以下是阿里云 Oss 的代码实现逻辑,可供参考:
+
+```java
+public class AliyunOssAttachmenetContainer implements AttachmentContainer {
+
+ private String basePath = PathKit.getWebRootPath();
+ private AliyunOssAttachmentConfig config = JbootConfigManager.me().get(AliyunOssAttachmentConfig.class);
+ private static ExecutorService fixedThreadPool = NamedThreadPools.newFixedThreadPool(3, "aliyun-oss-upload");
+
+
+ public AliyunOssAttachmenetContainer() {
+ }
+
+ @Override
+ public String saveFile(File file) {
+ if (!config.isEnable()) {
+ return null;
+ }
+ String relativePath = getRelativePath(file);
+ fixedThreadPool.execute(() -> {
+ upload(relativePath, file);
+ });
+ return relativePath;
+ }
+
+
+ @Override
+ public boolean deleteFile(String relativePath) {
+ if (!config.isEnable()) {
+ return false;
+ }
+ relativePath = removeFirstFileSeparator(relativePath);
+ OSSClient ossClient = createOSSClient();
+ try {
+ ossClient.deleteObject(config.getBucketname(), relativePath);
+ } catch (Exception ex) {
+ LogKit.error(ex.toString(), ex);
+ } finally {
+ ossClient.shutdown();
+ }
+ return true;
+ }
+
+
+ @Override
+ public File getFile(String relativePath) {
+ if (!config.isEnable()){
+ return null;
+ }
+ File localFile = new File(basePath, relativePath);
+ if (localFile.exists()) {
+ return localFile;
+ }
+ if (download(relativePath, localFile)) {
+ return localFile;
+ }
+ return null;
+ }
+
+
+ @Override
+ public String getRelativePath(File file) {
+ return FileUtil.removePrefix(file.getAbsolutePath(), basePath);
+ }
+
+
+ /**
+ * 同步本地文件到阿里云OSS
+ *
+ * @param path
+ * @param file
+ * @return
+ */
+ public boolean upload(String path, File file) {
+ if (StrUtil.isBlank(path)) {
+ return false;
+ }
+
+ path = removeFirstFileSeparator(path);
+ path = path.replace('\\', '/');
+
+ String ossBucketName = config.getBucketname();
+ OSSClient ossClient = createOSSClient();
+
+ try {
+ ossClient.putObject(ossBucketName, path, file);
+ boolean success = ossClient.doesObjectExist(ossBucketName, path);
+ if (!success) {
+ LogKit.error("aliyun oss upload error! path:" + path + "\nfile:" + file);
+ }
+ return success;
+
+ } catch (Throwable e) {
+ LogKit.error("aliyun oss upload error!!!", e);
+ return false;
+ } finally {
+ ossClient.shutdown();
+ }
+ }
+
+ /**
+ * 如果文件以 / 或者 \ 开头,去除 / 或 \ 符号
+ */
+ private static String removeFirstFileSeparator(String path) {
+ while (path.startsWith("/") || path.startsWith("\\")) {
+ path = path.substring(1);
+ }
+ return path;
+ }
+
+ /**
+ * 下载 阿里云 OSS 到本地
+ *
+ * @param path
+ * @param toFile
+ * @return
+ */
+ public boolean download(String path, File toFile) {
+ if (StrUtil.isBlank(path)) {
+ return false;
+ }
+ path = removeFirstFileSeparator(path);
+ OSSClient ossClient = createOSSClient();
+ try {
+ if (!toFile.getParentFile().exists()) {
+ toFile.getParentFile().mkdirs();
+ }
+
+ if (!toFile.exists()) {
+ toFile.createNewFile();
+ }
+ ossClient.getObject(new GetObjectRequest(config.getBucketname(), path), toFile);
+ return true;
+ } catch (Throwable e) {
+ LogKit.error("aliyun oss download error!!! path:" + path + " toFile:" + toFile, e);
+ if (toFile.exists()) {
+ toFile.delete();
+ }
+ return false;
+ } finally {
+ ossClient.shutdown();
+ }
+ }
+
+
+ private OSSClient createOSSClient() {
+ String endpoint = config.getEndpoint();
+ String accessId = config.getAccessKeyId();
+ String accessKey = config.getAccessKeySecret();
+ return new OSSClient(endpoint, new DefaultCredentialProvider(accessId, accessKey), null);
+ }
+
+
+}
+```
+
+```java
+@ConfigModel(prefix = "aliyunoss")
+public class AliyunOssAttachmentConfig {
+
+ private boolean enable = false;
+ private String endpoint;
+ private String accessKeyId;
+ private String accessKeySecret;
+ private String bucketname;
+ private boolean delSync;
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public void setEnable(boolean enable) {
+ this.enable = enable;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public String getAccessKeyId() {
+ return accessKeyId;
+ }
+
+ public void setAccessKeyId(String accessKeyId) {
+ this.accessKeyId = accessKeyId;
+ }
+
+ public String getAccessKeySecret() {
+ return accessKeySecret;
+ }
+
+ public void setAccessKeySecret(String accessKeySecret) {
+ this.accessKeySecret = accessKeySecret;
+ }
+
+ public String getBucketname() {
+ return bucketname;
+ }
+
+ public void setBucketname(String bucketname) {
+ this.bucketname = bucketname;
+ }
+
+ public boolean isDelSync() {
+ return delSync;
+ }
+
+ public void setDelSync(boolean delSync) {
+ this.delSync = delSync;
+ }
+}
+```
\ No newline at end of file
diff --git a/doc/docs/benchmark.md b/doc/docs/benchmark.md
new file mode 100644
index 0000000000000000000000000000000000000000..5b41d66959178b8185d360dec9db24b36902cedc
--- /dev/null
+++ b/doc/docs/benchmark.md
@@ -0,0 +1,109 @@
+---
+sidebar: auto
+---
+# Jboot 性能测试
+
+## 测试方式
+
+通过 apache benchmark 工具进行压力测试
+
+## 测试环境
+
+* JDK信息:
+ * java version "1.8.0_25"
+ * Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
+ * Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
+
+* 硬件信息
+ * 处理器:2.3 GHz Intel Core i7
+ * 内存:16 GB 1600 MHz DDR3
+ * 系统:macOS 10.13.4 (17E202)
+ * 硬件:MacBook Pro (Retina, 15-inch, Late 2013
+
+## 测试代码
+
+测试代码:
+
+```java
+@RequestMapping("/")
+public class HelloDemo extends JbootController {
+
+ public static void main(String[] args) {
+ Jboot.setBootArg("jboot.mode","product");
+ Jboot.run(args);
+ }
+
+ public void index() {
+ renderText("hello jboot ...");
+ }
+}
+```
+
+代码含义:
+
+* 默认使用 undertow 服务器
+* 把配置设置为生产模式,生产模式不会打印调试日志
+
+
+## 测试结果
+
+模拟10个并发,10000次访问:
+
+`ab -c10 -n10000 http://localhost:8080/`
+
+
+```
+This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
+Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
+Licensed to The Apache Software Foundation, http://www.apache.org/
+
+Benchmarking localhost (be patient)
+Completed 1000 requests
+Completed 2000 requests
+Completed 3000 requests
+Completed 4000 requests
+Completed 5000 requests
+Completed 6000 requests
+Completed 7000 requests
+Completed 8000 requests
+Completed 9000 requests
+Completed 10000 requests
+Finished 10000 requests
+
+
+Server Software:
+Server Hostname: localhost
+Server Port: 8080
+
+Document Path: /
+Document Length: 15 bytes
+
+Concurrency Level: 10
+Time taken for tests: 0.716 seconds
+Complete requests: 10000
+Failed requests: 0
+Total transferred: 2330000 bytes
+HTML transferred: 150000 bytes
+Requests per second: 13970.07 [#/sec] (mean)
+Time per request: 0.716 [ms] (mean)
+Time per request: 0.072 [ms] (mean, across all concurrent requests)
+Transfer rate: 3178.74 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 0 0.1 0 1
+Processing: 0 0 0.1 0 3
+Waiting: 0 0 0.1 0 3
+Total: 0 1 0.1 1 4
+
+Percentage of the requests served within a certain time (ms)
+ 50% 1
+ 66% 1
+ 75% 1
+ 80% 1
+ 90% 1
+ 95% 1
+ 98% 1
+ 99% 1
+ 100% 4 (longest request)
+ ```
\ No newline at end of file
diff --git a/doc/docs/build.md b/doc/docs/build.md
index 840323c013bf6e3249b9a2cceeadee3311c05377..ea6adace44f69c61c2311f52a29ba3878d8983f7 100644
--- a/doc/docs/build.md
+++ b/doc/docs/build.md
@@ -1,11 +1,12 @@
-# 项目构建
+# 项目打包
## 目录
-- 单模块 maven 项目构建
-- 多模块 maven 项目构建
+- 单模块 maven 项目打包
+- 多模块 maven 项目打包
+- fatjar 打包(全部打包到一个jar里)
-## 单模块 maven 项目构建
+## 单模块 maven 项目打包
在单一模块的maven项目开发中,我们通常在 `src/main/resources` 编写我们的配置文件,因此,在 maven 构建的时候,我们需要添加如下配置:
@@ -107,7 +108,7 @@ package.xml
在 项目的根目录下,创建 `jboot.sh` 文件,内容如下:
-```
+```shell
#!/bin/bash
# ----------------------------------------------------------------------
# name: jboot.sh
@@ -175,7 +176,7 @@ fi
复制该文件夹到服务器,然后执行里面的 `jboot.sh start` 命令即可上线。
-## 多模块 maven 项目构建
+## 多模块 maven 项目打包
多模块项目在以上配置的基础上,添加 `maven-resources-plugin` maven 插件,用于拷贝其他maven模块的资源文件和html等内容到此运行模块。
@@ -210,4 +211,167 @@ maven 配置如下:
```
-这部分可以参考 jpress 项目,网址:https://gitee.com/fuhai/jpress/blob/v2.0/starter/pom.xml
\ No newline at end of file
+这部分可以参考 jpress 项目,网址:https://gitee.com/fuhai/jpress/blob/v2.0/starter/pom.xml
+
+## fatjar 打包(全部打包到一个jar里)
+
+fatjar 打包指的是,把所有资源(html、css、js)以及项目依赖全部打包到一个 jar 包里,这样我们可以通过
+命令 `java -jar xxx.jar` 启动,更加方便部署,特别是方便在微服务下的多模块部署。
+
+fatjar 打包第一步,在 pom.xml 添加如下配置
+
+```xml
+
+
+
+ src/main/resources
+
+ **/*.*
+
+ false
+
+
+
+ src/main/webapp
+
+ **/*.*
+
+ false
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+ UTF-8
+ -parameters
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-resources
+ validate
+
+ copy-resources
+
+
+ ${basedir}/target/classes/webapp
+
+
+ ${basedir}/src/main/webapp
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.1.0
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+ io.jboot.app.JbootApplication
+
+
+
+
+
+ jar-with-dependencies
+
+
+
+
+
+
+
+```
+
+第二步:通过 Maven 打包
+
+执行命令 `mvn clean package` 进行打包,在 pom.xml 对应的模块下会生成一个 xxx-with-dependencies.jar 的 jar 包,复制该 jar
+到服务器上,执行 `java -jar xxx-with-dependencies.jar` 即可启动项目。
+
+注意:如果您使用了motan,那么在打包fatjar时需要将spi扩展声明文件需要使用追加模式,可参考
+
+```xml
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+
+ package
+
+ shade
+
+
+
+
+
+ META-INF/spring.schemas
+
+
+ META-INF/spring.handlers
+
+
+ META-INF/services/com.weibo.api.motan.registry.Registry
+
+
+ META-INF/services/com.weibo.api.motan.registry.RegistryFactory
+
+
+ META-INF/services/com.weibo.api.motan.rpc.Protocol
+
+
+ META-INF/services/com.weibo.api.motan.cluster.LoadBalance
+
+
+ META-INF/services/com.weibo.api.motan.cluster.Cluster
+
+
+ META-INF/services/com.weibo.api.motan.codec.Codec
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+```
diff --git a/doc/docs/cache.md b/doc/docs/cache.md
index 6e7a8afb010b72591097badc0937c74ccd6558e2..70bee8d2b2324aee42bb90420589c0164e437c9d 100644
--- a/doc/docs/cache.md
+++ b/doc/docs/cache.md
@@ -8,6 +8,8 @@
- EhCache
- Redis
- EhRedis
+- caffeine
+- caredis
- J2Cache
- NoneCache
@@ -18,21 +20,23 @@ Jboot 定位为高性能的微服务框架,然而高性能离不开合理的
- ehcache
- redis
- ehredis
+- caffeine
+- caredis
- j2cache
## 配置
-默认情况下,用户无需做任何配置就可以使用 Jboot 的缓存功能,默认情况下 Jboot 是使用 `Ehcache` 作为 Jboot 的缓存方案。
+默认情况下,用户无需做任何配置就可以使用 Jboot 的缓存功能,默认情况下 Jboot 是使用 `caffeine` 作为 Jboot 的缓存方案。
-如果需要修改把 `Ehcahce` 方案修改为使用 `redis` ,则可以添加如下的配置:
+如需把 `caffeine` 方案修改为使用 `redis` ,则可以添加如下的配置:
-```
+```properties
jboot.cache.type = redis
```
在使用 `redis` 作为默认的缓存方案时,需要配置上 `redis` 的相关信息,例如:
-```
+```properties
jboot.cache.redis.host = 127.0.0.1
jboot.cache.redis.port = 3306
jboot.cache.redis.password
@@ -54,7 +58,7 @@ jboot.cache.redis.serializer
```
当,以上未配置的时候,Jboot 自动会去寻找 `redis` 模块来使用,`redis` 的配置为:
-```
+```properties
jboot.redis.host
jboot.redis.port
jboot.redis.password
@@ -111,7 +115,7 @@ public class JbootRedisCacheImpl extends JbootCacheBase {
Jboot 提供了一个工具类 CacheUtil,我们可以直接通过 CacheUtil 来操作缓存。
-```
+```java
# 添加内容到缓存
CacheUtil.put("cacheName","key","value")
@@ -130,9 +134,9 @@ CacheUtil.setTtl("cacheName","key")
```
当一个系统有多个缓存组件的时候,可能有 redis 或者 ehcache 等,则可以使用如下use("type") 进行操作。
-```
-CacheUtil.use("redis").put("cacheName","key","value")
+```java
+CacheUtil.use("redis").put("cacheName","key","value")
CacheUtil.use("ehcache").put("cacheName","key","value")
```
@@ -190,3 +194,59 @@ public class CommentServiceImpl implements CommentService {
- `getCommentByIdWithCacheTime` 使用 `@Cacheable` 注解,但是添加了 `5秒` 的时间限制,因此,在 5秒钟内,无论调用多少次,返回的随机数都是一样的,5秒之后缓存被删除,再次调用之后会是一个新的随机数,新的随机数会继续缓存 5秒钟。
- `updateCache` 使用了注解 `@CachePut` ,每次调用此方法之后,会更新掉该 id 值的缓存
- `delCache` 使用了 `@CacheEvict` 注解,每次调用会删除该 id 值的缓存
+
+**@Cacheable 在 Controller 中使用时注意事项**
+
+默认情况下,@Cacheable 在 Controller 中使用时,系统会缓存 Request 的所有 Attributes 内容,这些内容可能来源于
+Controller 的 Action 通过 setAttr 进行设置,也可能来源于 Interceptor 设置。
+
+当下次请求的时候,Jboot 会直接使用缓存的数据进行直接渲染,而不再次执行 Controller 的 Action。
+
+但是,在很多时候,我们并不希望 Jboot 缓存所有的数据,比如:用户的登录状态。此时,我们可以通过如下的方法过滤掉缓存数据,
+保证每个请求都是最新的,而非缓存的数据。
+
+```java
+ActionCachedContent.addIgnoreAttr("ACCOUNT")
+```
+
+通过这个配置后,我们在拦截器 Interceptor 中添加的 ACCOUNT 属性,不会进行缓存。从而达到每次请求,都是都是最新的 ACCOUNT
+数据。
+
+```java
+class AccountInterceptor extends Interceptor {
+
+ public void intercept(Invocation inv) {
+ Accont account = accountService.findBy....
+ inv.getController().setAttr("ACCOUNT",account);
+
+ //....
+ }
+}
+```
+
+此时,虽然我们给 Controller 添加了 @Cacheable 属性,但是 ACCOUNT 的数据每个请求都是最新的。值得一提的是:
+
+```java
+ActionCachedContent.addIgnoreAttr("ACCOUNT")
+```
+是全局配置的,如果我们想要单独为每个请求配置不缓存的 attribute 时,可以在通过如下方式添加
+
+```java
+class AccountInterceptor extends Interceptor {
+
+ public void intercept(Invocation inv) {
+ Set ignoreAttrs = new HashSet<>();
+ ignoreAttrs.add("attr1");
+ ignoreAttrs.add("attr2");
+ ignoreAttrs.add("attr...");
+
+ //设置当前请求不进行缓存的 attr 属性
+ inv.getController().set(CacheableInterceptor.IGNORE_CACHED_ATTRS,ignoreAttrs);
+
+ Accont account = accountService.findBy....
+ inv.getController().setAttr("ACCOUNT", account);
+
+ //....
+ }
+}
+```
\ No newline at end of file
diff --git a/doc/docs/codegen.md b/doc/docs/codegen.md
index f43bf2d3b976befbb54ca6e44fd240645d8b857e..30dc3e38ffcc1e069cddc788485422dec9232d7c 100644
--- a/doc/docs/codegen.md
+++ b/doc/docs/codegen.md
@@ -2,7 +2,7 @@
Jboot 内置了一个简易的代码生成器,可以用来生成model层和Service层的基础代码,在生成代码之前,请先配置jboot.properties关于数据库相关的配置信息,Jboot 代码生成器会通过该配置去链接数据库。
-```
+```properties
jboot.datasource.type=mysql
jboot.datasource.url=jdbc:mysql://127.0.0.1:3306/jbootdemo
jboot.datasource.user=root
@@ -30,8 +30,8 @@ public class GenTester {
String baseModelPackage = "io.jboot.test.codegen.modelbase"; //生成的BaseModel的包名
//Model存放的路径,一般情况下是 /src/main/java 下,如下是放在 test 目录下
- String modelDir = PathKit.getWebRootPath() + "/src/test/java/" + modelPackage.replace(".", "/");
- String baseModelDir = PathKit.getWebRootPath() + "/src/test/java/" + baseModelPackage.replace(".", "/");
+ String modelDir = CodeGenHelpler.getUserDir() + "/src/test/java/" + modelPackage.replace(".", "/");
+ String baseModelDir = CodeGenHelpler.getUserDir() + "/src/test/java/" + baseModelPackage.replace(".", "/");
System.out.println("start generate...");
System.out.println("generate dir:" + modelDir);
@@ -46,14 +46,14 @@ public class GenTester {
//设置 service 层代码的存放目录
- String serviceOutputDir = PathKit.getWebRootPath() + "/src/test/java/" + servicePackage.replace(".", "/");
- String serviceImplOutputDir = PathKit.getWebRootPath() + "/src/test/java/" + serviceImplPackage.replace(".", "/");
+ String serviceOutputDir = CodeGenHelpler.getUserDir() + "/src/test/java/" + servicePackage.replace(".", "/");
+ String serviceImplOutputDir = CodeGenHelpler.getUserDir() + "/src/test/java/" + serviceImplPackage.replace(".", "/");
//开始生成代码
new JbootServiceInterfaceGenerator(servicePackage, serviceOutputDir, modelPackage).generate();
- new JbootServiceImplGenerator(servicePackage, serviceImplOutputDir, modelPackage).setImplName("provider").generate();
+ new JbootServiceImplGenerator(servicePackage, serviceImplPackage, serviceImplOutputDir, modelPackage).setImplName("provider").generate();
}
}
-````
\ No newline at end of file
+````
diff --git a/doc/docs/communication.md b/doc/docs/communication.md
index 6242f8b143fc9c3e97e53f4200e72bb9542c5efd..bb1d13fec172fba29d09e76418bcf55af4422579 100644
--- a/doc/docs/communication.md
+++ b/doc/docs/communication.md
@@ -8,5 +8,5 @@
- 微信群
- 
+ 
\ No newline at end of file
diff --git a/doc/docs/config.md b/doc/docs/config.md
index f34be1a95295aa8d41c3aeb42ef39fda6f55aaf9..c20efc35fa5d0fb35d807a0c9ba490ab5fabaa8a 100644
--- a/doc/docs/config.md
+++ b/doc/docs/config.md
@@ -7,10 +7,12 @@
- 描述
- 读取配置
- 注入配置
+- 动态配置
- 注解配置
- 配置实体类
+- 开启 Nacos 分布式配置中心
+- 开启 Apollo 分布式配置中心
- 配置内容加密解密
-- 设计原因
- 常见问题
- Jboot所有配置参考
@@ -19,12 +21,17 @@
在 Jboot 应用中,可以通过几下几种方式给 Jboot 应用进行配置。
- jboot.properties 配置文件
+- jboot-xxx.properties 配置文件
- 环境变量
- Jvm 系统属性
- 启动参数
+- 分布式配置中心(目前支持 Apollo 和 Nacos)
-> 注意:如果同一个属性被多处配置,那么 Jboot 读取配置的优先顺序是:
-> `启动参数` > `Jvm 系统属性` > `环境变量` > `jboot.properties 配置`
+> 注意:
+> 1、如果同一个属性被多处配置,那么 Jboot 读取配置的优先顺序是:
+> `分布式配置中心` > `启动参数` > `Jvm 系统属性` > `环境变量` > `jboot-xxx.properties` > `jboot.properties`。
+>
+> 2、jboot-xxx.properties 的含义是:当配置 jboot.app.mode=dev 时,默认去读取 jboot-dev.properties,同理当配置 jboot.app.mode=product 时,默认去读取 jboot-product.properties,jboot-xxx.properties 的文件名称是来源于 jboot.app.mode 的配置。jboot-xxx.properties 这个文件并不是必须的,但当该配置文件存在时,其优读取顺序先于 jboot.properties。
@@ -58,6 +65,39 @@ public class AopController extends JbootController {
}
```
+## 动态配置
+在 Jboot 的所有配置中,我们可以通过 ${key} 来指定替换为 value。
+
+示例1:
+
+```xml
+key1 = value1
+key2 = ${key1}/abc
+```
+
+那么读取到的 key2 的值为 `value1/abc`。
+
+示例2:
+
+```xml
+key1 = value1
+key2 = ${key1}/abc
+key3 = abc/${key2}/xyz
+```
+那么,key2 的值为 `value1/abc` ,key3 的值为 `abc/value1/abc/xyz`
+
+
+示例2:
+
+```xml
+key1 = value1
+key2 = ${otherkey}/abc
+```
+那么,因为系统中找不到 otherkey 的值,key2 的值为 `/abc`,如果我们在系统中,通过 `java -jar xxx.jar --otherkey=othervalue`,
+那么, key2 的值为 `othervalue/abc`
+
+
+
## 注解配置
在应用开发中,我们通常会使用注解,Jboot 内置了多个注解。
@@ -86,12 +126,13 @@ public class UserServiceProvider extends UserService{
}
```
-但是,无论是 `@RequestMapping("/user")` 或者是 `@RPCBean(group="myGroup",version="myVersion",port=...)` , 其参数配置都是固定的,因此,Jboot 提供了一种动态的配置方法,可以用于读取配置文件的内容。
+但是,无论是 `@RequestMapping("/user")` 或者是 `@RPCBean(group="myGroup",version="myVersion",port=...)` ,
+其参数配置都是固定的,因此,Jboot 提供了一种动态的配置方法,可以用于读取配置文件的内容。
例如:
```java
-@RequestMapping("${user.mapping}")
+@RequestMapping("${user.mapping}/abc")
public class UserController extends Controller{
//....
}
@@ -106,7 +147,7 @@ user.mapping = /user
其作用是等效于:
```java
-@RequestMapping("/user")
+@RequestMapping("/use/abcr")
public class UserController extends Controller{
//....
}
@@ -144,7 +185,7 @@ public class Component1Config{
private String password;
private long timeout;
- // 下方应该还有 getter setter, 略
+ // 下方应还有 getter setter, 此处略
}
```
@@ -158,15 +199,137 @@ Component1Config config = Jboot.config(Component1Config.class);
```
-> 备注:`@ConfigModel(prefix="component1")` 注解的含义是 `Component1Config` 的前缀是 `component1` ,因此,其属性 `host` 是来至配置文件的 `component1.host` 的值。
+配置内如如下:
+
+```properties
+component1.host = xxx
+component1.port = xxx
+component1.accout = xxx
+component1.password = xxx
+component1.timeout = xxx
+```
+
+> 备注:`@ConfigModel(prefix="component1")` 注解的含义是 `Component1Config` 的前缀是 `component1` ,因此,其属性 `host` 是来至配置文件的 `component1.host` 的值。
+
+
+
+
+## 开启 Nacos 分布式配置中心
+
+**第一步,添加 nacos 客户端的 Maven 依赖**
+
+```xml
+
+ com.alibaba.nacos
+ nacos-client
+ 1.3.2
+
+```
+
+
+
+**第二步:启动 nacos**
+
+- Clone Nacos 项目
+
+```
+git clone https://github.com/nacos-group/nacos-docker.git
+cd nacos-docker
+```
+
+单机模式 Derby
+```
+docker-compose -f example/standalone-derby.yaml up
+```
+
+单机模式 Mysql
+```
+docker-compose -f example/standalone-mysql.yaml up
+```
+
+集群模式
+```
+docker-compose -f example/cluster-hostname.yaml up
+```
+
+Nacos 控制台
+
+link:http://127.0.0.1:8848/nacos/
+
+nacos 的相关文档在
+
+https://nacos.io/zh-cn/docs/quick-start.html
+
+或者
+
+https://nacos.io/zh-cn/docs/quick-start-docker.html
+
+
+**第三步,在 jboot.properties 添加如下配置**
+
+```properties
+jboot.config.nacos.enable = true
+jboot.config.nacos.serverAddr = 127.0.0.1:8848
+jboot.config.nacos.dataId = jboot
+jboot.config.nacos.group = jboot
+```
+
+nacos 支持如下更多配置,但是只需要以上配置就可以正常运行。
+
+
+```properties
+jboot.config.nacos.isUseCloudNamespaceParsing = xxx
+jboot.config.nacos.isUseEndpointParsingRule = xxx
+jboot.config.nacos.endpoint = xxx
+jboot.config.nacos.endpointPort = xxx
+jboot.config.nacos.namespace = xxx
+jboot.config.nacos.username = xxx
+jboot.config.nacos.password = xxx
+jboot.config.nacos.accessKey = xxx
+jboot.config.nacos.secretKey = xxx
+jboot.config.nacos.ramRoleName = xxx
+jboot.config.nacos.serverAddr = xxx
+jboot.config.nacos.contextPath = xxx
+jboot.config.nacos.clusterName = xxx
+jboot.config.nacos.encode = xxx
+jboot.config.nacos.configLongPollTimeout = xxx
+jboot.config.nacos.configRetryTime = xxx
+jboot.config.nacos.maxRetry = xxx
+jboot.config.nacos.enableRemoteSyncConfig = xxx
+```
+
+## 开启 Apollo 分布式配置中心
+
+**第一步:添加 Apollo 客户端的 Maven 依赖**
+
+```xml
+
+ com.ctrip.framework.apollo
+ apollo-client
+ 1.7.0
+
+```
+
+**第二步,启动 Apollo**
+
+相关文档在 https://github.com/ctripcorp/apollo/wiki/Quick-Start
+
+**第三步,在 jboot.properties 添加如下配置**
+
+```
+jboot.config.apollo.enable = true
+jboot.config.apollo.appId = SampleApp
+jboot.config.apollo.meta = http://106.54.227.205:8080
+```
+
## 配置内容加密解密
-为了安全起见,很多时候我们需要对配置里的一些安全和隐私内容进行加密,比如数据库的账号密码等,防止web服务器被黑客入侵时保证数据库的安全。
+为了安全起见,我们需要对配置里的一些内容进行加密,比如数据库的账号、密码等,防止 web 服务器被黑客入侵时保证数据库的安全。
配置的内容加密是由用户自己编写加密算法。此时,Jboot 读取的只是加密的内容,为了能正常还原解密之后的内容,用户需要给 `JbootConfigManager` 配置上解密的实现 JbootConfigDecryptor。
-一般情况下,我们需要在 JbootAppListener 的 onInit() 里去配置。例如:
+一般情况下,我们需要在 JbootAppListener 的 `onInit()` 里去配置。例如:
```java
public MyApplicationListener implements JbootAppListener {
@@ -191,11 +354,6 @@ public MyConfigDecriptor implements JbootConfigDecryptor {
}
```
-## 设计原因
-
-由于 Jboot 定位是微服务框架,同时 Jboot 假设:基于 Jboot 开发的应用部署在 Docker 之上。
-
-因此,在做 Devops 的时候,编排工具(例如:k8s、mesos)会去修改应用的相关配置,而通过环境变量和启动配置,无疑是最方便快捷的。
## 常见问题
@@ -208,12 +366,19 @@ public MyConfigDecriptor implements JbootConfigDecryptor {
> 答:和启动参数一样,只需要把 `--` 换成 `-D`,例如: java -jar -Dundertow.port=8080 -Dundertow.host=0.0.0.0
-2、如何设置系统环境变量 ?
+3、如何设置系统环境变量 ?
> 答:在 Docker 下,启动 Docker 容器的时候,只需要添加 -e 参数即可,例如: `docker run -e undertow.port=8080 xxxx`
> Linux、Window、Mac 搜索引擎自行搜索关键字: `环境变量配置`
+注意:在设置的系统环境变量的key、value中,例如:jboot.app.mode = dev 可以修改为 JBOOT_APP_MODE = dev ,其他同理把全部小写
+修改为大写,符号点(.)修改为下划线(_)。
+
+
+## RPC 配置
+
+参考 [这里](./rpc.md)
-## Jboot 所有配置参考
+## Jboot 其他配置参考
```
undertow.devMode=true # 设置undertow为开发模式
@@ -267,7 +432,7 @@ jboot.datasource.type
jboot.datasource.url
jboot.datasource.user
jboot.datasource.password
-jboot.datasource.driverClassName = "com.mysql.jdbc.Driver"
+jboot.datasource.driverClassName = com.mysql.jdbc.Driver
jboot.datasource.connectionInitSql
jboot.datasource.poolName
jboot.datasource.cachePrepStmts = true
@@ -291,66 +456,6 @@ jboot.datasource.activeRecordPluginClass
jboot.datasource.needAddMapping = true //是否需要添加到映射,当不添加映射的时候,只能通过 model.use("xxx").save()这种方式去调用该数据源
-jboot.rpc.type
-jboot.rpc.callMode
-jboot.rpc.requestTimeOut
-jboot.rpc.registryType
-jboot.rpc.registryAddress
-jboot.rpc.registryName
-jboot.rpc.registryUserName
-jboot.rpc.registryPassword
-jboot.rpc.registryFile
-jboot.rpc.registryCheck
-jboot.rpc.consumerCheck
-jboot.rpc.providerCheck
-jboot.rpc.directUrl
-jboot.rpc.host
-jboot.rpc.defaultPort
-jboot.rpc.defaultGroup
-jboot.rpc.defaultVersion
-jboot.rpc.proxy
-jboot.rpc.filter
-jboot.rpc.serialization
-jboot.rpc.retries
-jboot.rpc.autoExportEnable
-
-jboot.rpc.dubbo.protocolName
-jboot.rpc.dubbo.protocolServer
-jboot.rpc.dubbo.protocolContextPath
-jboot.rpc.dubbo.protocolTransporter
-jboot.rpc.dubbo.protocolThreads
-jboot.rpc.dubbo.protocolHost
-jboot.rpc.dubbo.protocolPort
-jboot.rpc.dubbo.protocolContextpath
-jboot.rpc.dubbo.protocolThreadpool
-jboot.rpc.dubbo.protocolIothreads
-jboot.rpc.dubbo.protocolQueues
-jboot.rpc.dubbo.protocolAccepts
-jboot.rpc.dubbo.protocolCodec
-jboot.rpc.dubbo.protocolSerialization
-jboot.rpc.dubbo.protocolCharset
-jboot.rpc.dubbo.protocolPayload
-jboot.rpc.dubbo.protocolBuffer
-jboot.rpc.dubbo.protocolHeartbeat
-jboot.rpc.dubbo.protocolAccesslog
-jboot.rpc.dubbo.protocolExchanger
-jboot.rpc.dubbo.protocolDispatcher
-jboot.rpc.dubbo.protocolNetworker
-jboot.rpc.dubbo.protocolClient
-jboot.rpc.dubbo.protocolTelnet
-jboot.rpc.dubbo.protocolPrompt
-jboot.rpc.dubbo.protocolStatus
-jboot.rpc.dubbo.protocolRegister
-jboot.rpc.dubbo.protocolKeepAlive
-jboot.rpc.dubbo.protocolOptimizer
-jboot.rpc.dubbo.protocolExtension
-jboot.rpc.dubbo.protocolIsDefault
-jboot.rpc.dubbo.qosEnable
-jboot.rpc.dubbo.qosPort
-jboot.rpc.dubbo.qosAcceptForeignIp
-
-jboot.rpc.zbus.serviceName
-jboot.rpc.zbus.serviceToken
jboot.mq.type
jboot.mq.channel
diff --git a/doc/docs/db.md b/doc/docs/db.md
index 4b86e87ab6f3eb042b748038e97621b4991e286e..1fb31814224f8d40ed8b612130a5767ad5d54cc7 100644
--- a/doc/docs/db.md
+++ b/doc/docs/db.md
@@ -8,11 +8,12 @@ Jboot 数据库功能基于 JFinal 的 ActiveRecordPlugin 插件和 Apache shard
- 基本增删改查
- Db + Record 模式
- Model 映射方式
- - Column 查询方式
+ - Columns 查询方式
- 关联查询
- 分页查询
-- 批量插入
+- 一对一、一对多、多对一、多对多
- 事务操作
+- 多数据源
- 读写分离
- 分库分表
- 分布式事务
@@ -25,10 +26,73 @@ Jboot 的数据库是依赖 JFinal 的 ORM 做基本的数据库操作,同时
- Apache Sharding-Sphere 文档:http://shardingsphere.io/document/current/cn/overview/
- Seata 的帮助文档:https://github.com/seata/seata/wiki/Home_Chinese
+## 配置
+
+基本配置
+
+```properties
+jboot.datasource.type=mysql
+jboot.datasource.url=jdbc:mysql://127.0.0.1:3306/dbname
+jboot.datasource.user=root
+jboot.datasource.password=123456
+```
+
+更多配置
+```properties
+# 数据源名称
+jboot.datasource.name
+# 数据源类型,支持 mysql oracle sqlserver sqlite ansisql postgresql clickhouse 等
+jboot.datasource.type
+# 链接url
+jboot.datasource.url
+# 数据库登录账号
+jboot.datasource.user
+#数据库登录密码
+jboot.datasource.password
+#数据源驱动,一般只需要配置 type,程序自动会寻找适合的驱动
+jboot.datasource.driverClassName = com.mysql.jdbc.Driver
+jboot.datasource.connectionInitSql
+jboot.datasource.poolName
+jboot.datasource.cachePrepStmts = true
+jboot.datasource.prepStmtCacheSize = 500
+jboot.datasource.prepStmtCacheSqlLimit = 2048
+jboot.datasource.maximumPoolSize = 10
+jboot.datasource.maxLifetime
+jboot.datasource.idleTimeout
+jboot.datasource.minimumIdle = 0
+
+#sql 模板文件存放路径
+jboot.datasource.sqlTemplatePath
+# sql 模板文件的名称,多个模板文件使用英文都还隔开
+jboot.datasource.sqlTemplate
+
+#数据源创建的工厂类 DataSourceFactory
+jboot.datasource.factory
+#分布式分库分表的 sharding 配置文件
+jboot.datasource.shardingConfigYaml
+#jfinal 的 dbPro 的创建工厂类,该类需要实现 IDbProFactory 接口
+jboot.datasource.dbProFactory
+#jfinal ActiveRecordPlugin 插件里需要配置的 containerFactory,填写类名
+jboot.datasource.containerFactory
+#jfinal 默认配置的事务隔离级别
+jboot.datasource.transactionLevel
+# 此数据源包含哪些表
+jboot.datasource.table
+# 该数据源排除哪些表
+jboot.datasource.exTable
+# 数据方言的 class
+jboot.datasource.dialectClass
+# 自定义的 activeRecordPlugin 类,该类需要继承 ActiveRecordPlugin
+jboot.datasource.activeRecordPluginClass
+# 是否需要添加到映射,当不添加映射的时候,只能通过 model.use("xxx").save()这种方式去调用该数据源
+jboot.datasource.needAddMapping = true
+```
+
## 基本增删改查
JFinal 操作数据库,提供了两种方式对数据库进行操作,他们分别是:
+
- Db + Record 方式
- Model 映射方式
@@ -38,9 +102,9 @@ JFinal 操作数据库,提供了两种方式对数据库进行操作,他们
参考 JFinal 的文档:https://jfinal.com/doc/5-5
-### Model 映射方式
+### 使用 @Table 注解来玩喷子和 Model 映射
-Model是 MVC 模式中的 M 部分。以下是 Model 定义示例代码:
+Model 是 MVC 模式中的 M 部分。以下是 Model 定义示例代码:
```java
@Table(tableName = "user", primaryKey = "id")
@@ -68,8 +132,9 @@ public abstract class BaseUser> extends JbootModel impl
```
需要注意的是:
+
- 以上的 `User` 和 `BaseUser` 都是通过代码生成器自动生成的,无需手写。
-- 多次执行代码生成器,`User` 代码不会被覆盖,但是 `BaseUser` 会被重新覆盖,因此,请不要在 `BaseUser` 手写任何代码。
+- 多次执行代码生成器,`User` 代码不会被覆盖,但是 `BaseUser` 会被重新覆盖,因此,请不要在 `BaseUser` 手写任何代码。
一般情况下,在正式的项目里,代码分层还需要 `Service` 层来对业务逻辑进行处理。
@@ -123,18 +188,6 @@ CREATE TABLE `user` (
`email` varchar(64) DEFAULT NULL COMMENT '邮件',
`mobile` varchar(32) DEFAULT NULL COMMENT '手机电话',
`gender` varchar(16) DEFAULT NULL COMMENT '性别',
- `signature` varchar(2048) DEFAULT NULL COMMENT '签名',
- `birthday` datetime DEFAULT NULL COMMENT '生日',
- `company` varchar(256) DEFAULT NULL COMMENT '公司',
- `occupation` varchar(256) DEFAULT NULL COMMENT '职位、职业',
- `address` varchar(256) DEFAULT NULL COMMENT '地址',
- `zipcode` varchar(128) DEFAULT NULL COMMENT '邮政编码',
- `site` varchar(256) DEFAULT NULL COMMENT '个人网址',
- `graduateschool` varchar(256) DEFAULT NULL COMMENT '毕业学校',
- `education` varchar(256) DEFAULT NULL COMMENT '学历',
- `avatar` varchar(256) DEFAULT NULL COMMENT '头像',
- `idcardtype` varchar(128) DEFAULT NULL COMMENT '证件类型:身份证 护照 军官证等',
- `idcard` varchar(128) DEFAULT NULL COMMENT '证件号码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表,保存用户信息。';
```
@@ -168,6 +221,584 @@ public List findListBy(int userAge,String articleTitle){
}
```
+
+
+## 一对一、一对多、多对一、多对多
+
+在 Jboot 中,提供了 Join 系列方法,我们在 Service 层可以直接使用 Join 进行 一对一、一对多、多对一、多对多 的查询操作。
+
+假设存在这样的关系:一篇文章只有一个作者,一个作者可以写多篇文章,一篇文章可以归属多个文章分类、一个文章分类有可以包含多篇文章。
+
+
+
+那么,表结构设计如下:
+
+
+
+文章表 article :
+
+```sql
+CREATE TABLE `article` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `author_id` int(11) unsigned DEFAULT NULL COMMENT '文章作者ID',
+ `title` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文章标题',
+ `content` text COLLATE utf8mb4_unicode_ci COMMENT '文章内容',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+
+
+文章作者表 author :
+
+```sql
+CREATE TABLE `author` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `nickname` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作者昵称',
+ `email` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作者邮件',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+
+
+文章分类表 category :
+
+```sql
+CREATE TABLE `category` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `title` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分类标题',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '分类描述',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+
+
+文章分类和分类的 多对多关系表: article_category:
+
+```sql
+CREATE TABLE `article_category` (
+ `article_id` int(11) unsigned NOT NULL COMMENT '文章ID',
+ `category_id` int(11) unsigned DEFAULT NULL COMMENT '分类ID'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+
+
+数据内容如下:
+
+
+
+```sql
+INSERT INTO `article` (`id`, `author_id`, `title`, `content`)
+VALUES
+ (1,1,'文章1','内容111'),
+ (2,1,'文章2','内容2222'),
+ (3,2,'文章3','内容333'),
+ (4,2,'文章4','内容444');
+```
+有 4 篇文章。
+
+
+```sql
+INSERT INTO `author` (`id`, `nickname`, `email`)
+VALUES
+ (1,'孙悟空','swk@gmail.com'),
+ (2,'猪八戒','zbj@gmail.com');
+```
+有 2 个作者。
+
+```sql
+INSERT INTO `category` (`id`, `title`, `description`)
+VALUES
+ (1,'文章分类1','文章分类描述111'),
+ (2,'文章分类2','文章分类描述222');
+```
+有 2 个文章分类。
+
+
+```sql
+INSERT INTO `article_category` (`article_id`, `category_id`)
+VALUES
+ (1,1),
+ (1,2),
+ (2,2),
+ (3,1),
+ (3,2),
+ (4,1);
+```
+文章 1 有两个分类;文章 2 有一个分类; 文章 3 有两个分类; 文章 4 有一个分类。
+
+
+
+这样,在我们的代码里应该存在 3 个Service,分别是:
+
+- ArticleService 用于查询文章相关的服务
+- CategoryService 用于查询文章分类相关的服务
+- AuthorService 用于查询作者的服务
+
+
+
+ArticleService 代码如下:
+
+```java
+public class ArticleService extends JbootServiceBase {
+
+ @Inject
+ private AuthorService authorService;
+
+ @Inject
+ private CategoryService categoryService;
+
+ /**
+ * 查询出文章列表,并自动查询其归属的多个分类,已经文章的作者
+ * @return
+ */
+ public List findListWithAuthorAndCategories(){
+ List articles = DAO.findAll();
+
+ // 查询出每篇文章的作者,关系字段的字段名称
+ authorService.join(articles,"author_id");
+
+ // 查询文章的所属分类,三个参数分别是:中间表名称,article表对应的字段,分类表对应的字段
+ categoryService.joinManyByTable(articles,"article_category","article_id","category_id");
+
+ return articles;
+ }
+}
+```
+
+ArticleService 输出的 Json 内容如下:
+
+```json
+[
+ {
+ "id":1,
+ "authorId":1,
+ "title":"文章1",
+ "content":"内容111",
+ "author":{
+ "nickname":"孙悟空",
+ "id":1,
+ "email":"swk@gmail.com"
+ },
+ "categoryList":[
+ {
+ "description":"文章分类描述111",
+ "id":1,
+ "title":"文章分类1"
+ },
+ {
+ "description":"文章分类描述222",
+ "id":2,
+ "title":"文章分类2"
+ }
+ ]
+ },
+ {
+ "id":2,
+ "authorId":1,
+ "title":"文章2",
+ "content":"内容2222",
+ "author":{
+ "nickname":"孙悟空",
+ "id":1,
+ "email":"swk@gmail.com"
+ },
+ "categoryList":[
+ {
+ "description":"文章分类描述222",
+ "id":2,
+ "title":"文章分类2"
+ }
+ ]
+ },
+ {
+ "id":3,
+ "authorId":2,
+ "title":"文章3",
+ "content":"内容333",
+ "author":{
+ "nickname":"猪八戒",
+ "id":2,
+ "email":"zbj@gmail.com"
+ },
+ "categoryList":[
+ {
+ "description":"文章分类描述111",
+ "id":1,
+ "title":"文章分类1"
+ },
+ {
+ "description":"文章分类描述222",
+ "id":2,
+ "title":"文章分类2"
+ }
+ ]
+ },
+ {
+ "id":4,
+ "authorId":2,
+ "title":"文章4",
+ "content":"内容444",
+ "author":{
+ "nickname":"猪八戒",
+ "id":2,
+ "email":"zbj@gmail.com"
+ },
+ "categoryList":[
+ {
+ "description":"文章分类描述111",
+ "id":1,
+ "title":"文章分类1"
+ }
+ ]
+ }
+]
+```
+
+
+
+
+AuthorService 代码如下:
+
+```java
+public class AuthorService extends JbootServiceBase {
+
+ @Inject
+ private ArticleService articleService;
+
+ public List findListWithArticles(){
+ List authors = DAO.findAll();
+
+ //查询每个作者的所有文章,第二个参数是关系字段的名称
+ articleService.joinMany(authors,"author_id");
+
+ return authors;
+ }
+}
+```
+
+AuthorService 输出的 Json 内容如下:
+
+```json
+[
+ {
+ "id":1,
+ "nickname":"孙悟空",
+ "email":"swk@gmail.com",
+ "articleList":[
+ {
+ "id":1,
+ "authorId":1,
+ "title":"文章1",
+ "content":"内容111"
+ },
+ {
+ "id":2,
+ "authorId":1,
+ "title":"文章2",
+ "content":"内容2222"
+ }
+ ]
+ },
+ {
+ "id":2,
+ "nickname":"猪八戒",
+ "email":"zbj@gmail.com",
+ "articleList":[
+ {
+ "id":3,
+ "authorId":2,
+ "title":"文章3",
+ "content":"内容333"
+ },
+ {
+ "id":4,
+ "authorId":2,
+ "title":"文章4",
+ "content":"内容444"
+ }
+ ]
+ }
+]
+```
+
+
+
+
+CategoryService 代码如下:
+
+```java
+public class CategoryService extends JbootServiceBase {
+
+ @Inject
+ private ArticleService articleService;
+
+ public List findListWithArticles(){
+ List categories = DAO.findAll();
+
+ //查询每个分类的所有文章
+ articleService.joinManyByTable(categories,"article_category","category_id","article_id");
+
+ return categories;
+ }
+}
+```
+
+CategoryService 输出的 json 内容如下:
+
+```json
+[
+ {
+ "articleList":[
+ {
+ "id":1,
+ "authorId":1,
+ "title":"文章1",
+ "content":"内容111"
+ },
+ {
+ "id":3,
+ "authorId":2,
+ "title":"文章3",
+ "content":"内容333"
+ },
+ {
+ "id":4,
+ "authorId":2,
+ "title":"文章4",
+ "content":"内容444"
+ }
+ ],
+ "description":"文章分类描述111",
+ "id":1,
+ "title":"文章分类1"
+ },
+ {
+ "articleList":[
+ {
+ "id":1,
+ "authorId":1,
+ "title":"文章1",
+ "content":"内容111"
+ },
+ {
+ "id":2,
+ "authorId":1,
+ "title":"文章2",
+ "content":"内容2222"
+ },
+ {
+ "id":3,
+ "authorId":2,
+ "title":"文章3",
+ "content":"内容333"
+ }
+ ],
+ "description":"文章分类描述222",
+ "id":2,
+ "title":"文章分类2"
+ }
+]
+```
+
+
+
+具体代码参考:[这个链接](https://gitee.com/JbootProjects/jboot/tree/master/src/test/java/io/jboot/test/join)
+
+
+
+
+
+## 多数据源
+
+默认单数据源的情况下,我们需要在 `jboot.properties` 添加如下配置:
+
+```
+jboot.datasource.type=mysql
+jboot.datasource.url=jdbc:mysql://127.0.0.1:3306/jbootdemo
+jboot.datasource.user=root
+jboot.datasource.password=your_password
+```
+
+如果是多个数据源,我们可以在 `jboot.datasource.` 后面添加数据源的名称,例如:
+
+```
+jboot.datasource.a1.type=mysql
+jboot.datasource.a1.url=jdbc:mysql://127.0.0.1:3306/jboot1
+jboot.datasource.a1.user=root
+jboot.datasource.a1.password=your_password
+
+jboot.datasource.a2.type=mysql
+jboot.datasource.a2.url=jdbc:mysql://127.0.0.1:3306/jboot2
+jboot.datasource.a2.user=root
+jboot.datasource.a2.password=your_password
+```
+
+这表示,我们又增加了数据源 `a1` 和数据源 `a2`,在使用的时候,我们只需要做以下使用:
+
+```java
+Company company = new Company();
+company.setId(1);
+company.setName("name");
+
+company.use("a1").save();
+```
+
+`company.use("a1").save();` 表示使用数据源 `a1` 进行保存。
+
+
+**需要注意的是:**
+
+在多数据源应用中,很多时候,我们的一个 Model 只有对应一个数据源,而不是一个 Model 对应多个数据源。假设 `company` 只有在 `a1` 数据源中存在,在其他数据源并不存在,我们需要把 `a1` 数据源的配置修改如下:
+
+```
+jboot.datasource.a1.type=mysql
+jboot.datasource.a1.url=jdbc:mysql://127.0.0.1:3306/jboot1
+jboot.datasource.a1.user=root
+jboot.datasource.a1.password=your_password
+jboot.datasource.a1.table=company
+
+jboot.datasource.a2.type=mysql
+jboot.datasource.a2.url=jdbc:mysql://127.0.0.1:3306/jboot2
+jboot.datasource.a2.user=root
+jboot.datasource.a2.password=your_password
+jboot.datasource.a2.table=user,xxx (其他非company表)
+```
+
+这样,`company` 在 `a1` 数据源中存在,Jboot在初始化的时候,并不会去检查 `company` 在其他数据源中是否存在,同时,代码操作 `company` 的时候,不再需要 `use()` ,代码如下:
+
+
+```java
+Company company = new Company();
+company.setCid("1");
+company.setName("name");
+
+//company.use("a1").save();
+company.save();
+```
+
+代码中不再需要 `use("a1")` 指定数据源,因为 `company` 表只有一个数据源。
+
+## 事务以及 @Transactional 注解
+
+在 JFinal 中提供了一种使用 Db.tx(...) 的操作方式,具体参考:https://jfinal.com/doc/5-7 ,同时,Jboot 也提供了一个用于进行事务管理的注解
+@Transactional 。具体用法如下:
+
+实例1:出现异常时回滚
+```java
+@Transactional
+public void test1() {
+ //your code
+ throw new RuntimeException("...");
+}
+```
+
+实例2:返回值为 false 时回滚
+```java
+@Transactional(rollbackForFalse=true)
+public boolean test2() {
+ //your code
+ return false;
+}
+```
+
+实例3:返回值为 Ret.fail() 时回滚
+```java
+@Transactional(rollbackForRetFail=true)
+public Ret test3() {
+ //your code
+ return Ret.fail("message...");
+}
+```
+
+实例4:返回值为 null 时回滚
+```java
+@Transactional(rollbackForNull=true)
+public Ret test3() {
+ //your code
+ return null;
+}
+```
+
+@Transactional 的更多配置:
+```java
+public @interface Transactional {
+
+ /**
+ * 使用哪个数据源
+ *
+ * @return
+ */
+ String config() default "";
+
+ /**
+ * 事务隔离级别
+ *
+ * @return
+ */
+ int transactionLevel() default -1;
+
+ /**
+ * return false 的时候,是否进行回滚
+ *
+ * @return
+ */
+ boolean rollbackForFalse() default false;
+
+ /**
+ * return ret.fail 的时候,是否进行回滚
+ *
+ * @return
+ */
+ boolean rollbackForRetFail() default false;
+
+
+ /**
+ * 返回 null 的时候,是否进行回滚
+ *
+ * @return
+ */
+ boolean rollbackForNull() default false;
+
+
+ /**
+ * 配置允许哪些异常不回滚
+ *
+ * @return
+ */
+ Class extends Throwable>[] noRollbackFor() default {};
+
+ /**
+ * 是否在新的线程里执行,在 Controller 的 Action 方法下配置无效
+ * 在 Controller 里,不能以新的线程在运行
+ *
+ * @return
+ */
+ boolean inNewThread() default false;
+
+ /**
+ * 使用哪个线程池来运行线程,需要在启动的时候,通过 TransactionalManager 来配置线程池及其名称
+ *
+ * @return
+ */
+ String threadPoolName() default "";
+
+ /**
+ * 是否以阻塞的方式运行线程,这个配置只有在返回值 void 情况下配置生效
+ * 有返回值的,此配置无效,默认都是阻塞运行线程的方式运行
+ *
+ * @return
+ */
+ boolean threadWithBlocked() default false;
+
+}
+```
+
+
+
## 读写分离
在 Jboot 应用中,读写分离建议使用两个数据源,分别是读的数据源和写的数据源,写的数据源必须支持可读可写。
@@ -178,7 +809,7 @@ public List findListBy(int userAge,String articleTitle){
Jboot 的分库分表功能使用了 Sharding-jdbc 实现的,若在 Jboot 应用在需要用到分库分表功能,需要添加 `jboot.datasource.shardingConfigYaml = xxx.yaml ` 的配置,其中 `xxx.yaml` 配置需要放在 classpath 目录下,配置内容参考:https://shardingsphere.apache.org/document/current/cn/manual/sharding-jdbc/configuration/config-yaml/
-**注意:** 当在 `jboot.properties` 文件配置 `jboot.datasource.shardingConfigYaml = xxx.yaml`之后,不再需要在 `jboot.properties` 配置 `jboot.datasource.url` 、 `jboot.datasource.user` 和 `jboot.datasource.password` 等,这些配置都转义到 `xxx.yaml` 里进行配置了。
+**注意:** 当在 `jboot.properties` 文件配置 `jboot.datasource.shardingConfigYaml = xxx.yaml`之后,不再需要在 `jboot.properties` 配置 `jboot.datasource.url` 、 `jboot.datasource.user` 和 `jboot.datasource.password` 等,这些配置都转移到 `xxx.yaml` 里进行配置了。
## 分布式事务
@@ -429,4 +1060,4 @@ support {
> 1、jboot.seata.txServiceGroup 配置的值要注意和 file.conf 里的 vgroup_mapping.xxx 保持一致
> 2、jboot.rpc.filter=seata ##seata在Dubbo中的事务传播过滤器
-以上配置完毕后如何使用呢?点击 [这里](../../src/test/java/io/jboot/test/seata) 查看代码实例。
\ No newline at end of file
+以上配置完毕后如何使用呢?点击 [这个链接](https://gitee.com/JbootProjects/jboot/tree/master/src/test/java/io/jboot/test/seata) 查看代码实例。
\ No newline at end of file
diff --git a/doc/docs/deploy.md b/doc/docs/deploy.md
index 6486679fcdfb59d1f45040b72a0ac510c9fd8daf..d91d14be8c66973560e0969164e4a98d953f6237 100644
--- a/doc/docs/deploy.md
+++ b/doc/docs/deploy.md
@@ -2,15 +2,101 @@
## 目录
- 描述
+- 通过 脚本 运行
- 通过 Jar 运行
- 通过 Tomcat 运行
## 描述
+本文档提供了 3 种部署方式,对应 Jboot 里的 3 种[打包方式](./build.md)。
+
+## 通过 脚本 运行
+
+在 [打包方式](./build.md) 文档中,我们可以把项目打包成一个 .zip 的压缩包项目,里面带有 jboot.sh (和 jboot.bat) 执行脚本,
+只需要我们解压 .zip 压缩文件,通过如下命令就可以对 jboot 项目进行启动和停止。
+
+```shell
+# 启动
+./jboot.sh start
+
+# 停止
+./jboot.sh stop
+
+# 重启
+./jboot.sh restart
+```
+
+在 Windows 系统中,通过如下命令执行
+
+```shell
+# 启动
+jboot.bat start
+
+# 停止
+jboot.bat stop
+
+# 重启
+jboot.bat restart
+```
## 通过 Jar 运行
-Jboot 通过依赖 `JFinal-Undertow` 内置了 Undertow 服务器,可以直接通过 Jar 的方式进行运行,这部分直接参考文档:https://www.jfinal.com/doc/1-3 即可。
+在 [打包方式](./build.md) 文档中,我们可以把所有的资源文件(html、css、js、配置文件 等)以及项目的所有依赖打包到一个 jar 包
+里去,打包成功后,可以通过如下命令运行。
+
+启动(前台启动,命令窗口不能关闭)
+```shell
+java -jar xxx.jar
+```
+> 当前ssh窗口(命令窗口)被锁定,可按 `CTRL + C` 打断程序运行,或直接关闭窗口,程序退出。
+
+启动(后台启动,命令窗口不能关闭)
+```shell
+java -jar xxx.jar &
+```
+> & 代表在后台运行。当前ssh窗口不被锁定,但是当窗口关闭时,程序中止运行。
+
+
+启动(后台启动)
+```shell
+nohup java -jar xxx.jar &
+```
+>nohup 意思是不挂断运行命令,当账户退出或终端关闭时,程序仍然运行,当用 nohup 命令执行作业时,缺省情况下该作业的所有输出被重定向到nohup.out的文件中,除非另外指定了输出文件。
+
+启动(后台启动)
+```shell
+nohup java -jar xxx.jar>temp.txt &
+```
+>command >out.file 是将 command 的输出重定向到 out.file 文件,即输出内容不打印到屏幕上,而是输出到 out.file 文件中。
+>以上命令,就是把 java 启动的输出,输入到 temp.txt 文件里,而不是在屏幕上。
+
+
+另外:可以通过如下命令实时查看日志:
+
+```shell
+tail -f temp.text
+```
+
+可通过jobs命令查看后台运行任务
+
+```shell
+jobs
+```
+
+jobs 命令就会列出所有后台执行的作业,并且每个作业前面都有个编号。如果想将某个作业调回前台控制,只需要 fg + 编号即可。
+
+```shell
+fg 33
+```
+
+查看程序端口的进程的pid
+
+```shell
+netstat -nlp | grep:8080
+```
+
+
+
## 通过 Tomcat 运行
diff --git a/doc/docs/docker.md b/doc/docs/docker.md
index 0482cbc30de187aff3c166eb2ab269bfc7f8b05e..f1a1b601ccd8ba638fbb08034b2f11e1f7be029e 100644
--- a/doc/docs/docker.md
+++ b/doc/docs/docker.md
@@ -6,9 +6,9 @@
例如,我们需要给应用配置数据库信息:
-```
+```shell
docker run
--e JBOOT_DATASOURCE_URL="jdbc:mysql://127.0.0.1:3306/jpress3"
+-e JBOOT_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/jpress3
-e JBOOT_DATASOURCE_USER=root
-e JBOOT_DATASOURCE_PASSWORD=123456
jpress:v2.0.8
@@ -16,7 +16,7 @@ jpress:v2.0.8
这个启动命令,等同于在 jboot.properties 添加如下的配置
-```
+```properties
jboot.datasource.url=jdbc:mysql://127.0.0.1:3306/jpress3
jboot.datasource.user=root
jboot.datasource.password=123456
diff --git a/doc/docs/dubbo3-upgrade.md b/doc/docs/dubbo3-upgrade.md
new file mode 100644
index 0000000000000000000000000000000000000000..1c0c698b40ceb47161e2aba845142bf41867f9d6
--- /dev/null
+++ b/doc/docs/dubbo3-upgrade.md
@@ -0,0 +1,45 @@
+# Dubbo2 升级 Dubbo3 文档
+
+dubbo2 升级 dubbo3 ,请先阅读 dubbo3 官网的升级文档:https://dubbo.apache.org/zh/docs/migration/migration-service-discovery/
+
+
+总体上来说,3.x 是完全兼容 2.x 版本的,因此,理论上只需要添加两个配置即可:
+
+- 1、生产端增加配置:
+ jboot.rpc.dubbo.registry.registerMode=instance
+
+- 2、消费端配置:
+ jboot.rpc.application.service-discovery.migration=FORCE_APPLICATION
+
+
+**以下是升级流程:**
+
+- 1、 修改工程 pom.xml 中的 dubbo 依赖到最新版本就可以完成升级,其它代码不用动,升级完成后,建议以下参数进行定制调整(2、3步)
+
+- 2、Provider 端 `jboot.properties` 中
+ `jboot.rpc.dubbo.registry.registerMode` 的可选值 interface、instance、all,默认是 all,即接口级地址、应用级地址都注册。
+
+ - instance: 应用级注册;
+ - interface:接口级注册;
+ - all:双注册(默认值) 即接口和应用都同时注册到注册中心中,双注册不可避免的会带来额外的注册中心存储压力,
+ 建议修改默认值为 instance 模式,这是 dubbo3 推荐方式。
+
+- 3、Consumer 端 `jboot.properties` 中 `jboot.rpc.application.service-discovery.migration`
+的可选值有:
+ - FORCE_INTERFACE:只消费接口级地址,如无地址则报错,单订阅 2.x 地址
+ - APPLICATION_FIRST:智能决策接口级/应用级地址,双订阅
+ - FORCE_APPLICATION:只消费应用级地址,如无地址则报错,单订阅 3.x 地址
+jboot.rpc.dubbo.application.service-discovery.migration=APPLICATION_FIRST
+
+> 注意事项:
+>
+> 在 Dubbo3 中,服务端(Provider端)如果采用应用级注册 (instance),注册中心中只会注册应用实例信息,不会注册接口服务信息。接口服务信息存储在元数据中心,在元数据中心存储了实例和接口的映射关系。消费端通过元数据映射关系获取所需要的服务信息。
+>
+> 这套流程是 dubbo3 为了兼容 dubbo2 自动来完成的,官方名称为:服务自省。详情:
+https://dubbo.apache.org/zh/docs/examples/service-discovery/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%8D%E5%8A%A1%E8%87%AA%E7%9C%81
+
+
+## 参考代码
+
+https://gitee.com/JbootProjects/jboot/tree/master/src/test/java/io/jboot/test/rpc/dubbo3
+
diff --git a/doc/docs/event.md b/doc/docs/event.md
index 4cfb3693a343c7622e81b8615f3f86f8095eba8b..10f800ca445386806a4acdd74c755c786ba271b3 100644
--- a/doc/docs/event.md
+++ b/doc/docs/event.md
@@ -60,7 +60,8 @@ public class MyEventListener implements JbootEventListener {
此时,当有 `eventName` , `event1` , `event2` 任何一个事件发送的时候,以上的 `MyEventListener.onEvent` 都会被触发执行。
**备注**
-事件机制和 MQ(消息队列)很相似,主要区别是在于事件机制只是在 Jvm 应用内执行,解决的是业务直接的耦合问题。
+
+事件机制和 MQ(消息队列)很相似,主要区别是在于事件机制只是在 Jvm 应用内执行,解决的业务之间的耦合问题。
MQ(消息队列)解决的是分布式下,多个系统的事件(或者消息)发送和监听,MQ 需要第三方的 **中间件** ,例如:Redis、rabbitMQ、activeMQ...等。
diff --git a/doc/docs/gateway.md b/doc/docs/gateway.md
new file mode 100644
index 0000000000000000000000000000000000000000..c1928b712bfc97925bfa832594bb09ee264c79e7
--- /dev/null
+++ b/doc/docs/gateway.md
@@ -0,0 +1,409 @@
+# 网关
+
+## 目录
+
+- 概述
+- path路由
+- host路由
+- query路由
+- 多个 Gateway 配置
+- 服务发现
+- 注意事项
+
+
+## 概述
+
+Jboot 已经内置基础的网关,网关功能目前暂时只能通过在 jboot.properties 文件进行配置。
+
+如下是一个正常的 gateway 配置。
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+
+jboot.gateway.uriHealthCheckEnable = true
+jboot.gateway.uriHealthCheckPath = /your-health-check-path
+
+jboot.gateway.sentinelEnable = false
+jboot.gateway.sentinelBlockPage = /block
+jboot.gateway.sentinelBlockJsonMap = message:xxxx;code:200
+
+jboot.gateway.proxyReadTimeout = 10000
+jboot.gateway.proxyConnectTimeout = 5000
+jboot.gateway.proxyContentType = text/html;charset=utf-8
+
+jboot.gateway.interceptors = com.xxx.Interceptor1,com.xxx.Interceptor2
+jboot.gateway.loadBalanceStrategy = com.xxx.loadBalanceStrategy1
+
+jboot.gateway.pathEquals = /path
+jboot.gateway.pathContains = /path
+jboot.gateway.pathStartsWith = /path
+jboot.gateway.pathEndswith = /path
+
+jboot.gateway.hostEquals = xxx.com
+jboot.gateway.hostContains = xxx.com
+jboot.gateway.hostStartsWith = xxx.com
+jboot.gateway.hostEndswith = xxx.com
+
+jboot.gateway.queryEquals = aa:bb,cc:dd
+jboot.gateway.queryContains = aa,bb
+```
+
+
+
+- name 设置路由的名称
+- uri 设置路由目标网址,可以配置多个 uri,多个 uri 用英文逗号(,) 隔开,当有多个 uri 的时候,系统会 **随机** 使用其中一个去访问
+- enable 是否启用该路由
+- uriHealthCheckEnable 是否启用健康检查功能
+- uriHealthCheckPath URI 健康检查路径,当配置 uriHealthCheckPath 后,健康检查的 url 地址为 uri + uriHealthCheckPath,当健康检查目标网址的 http code 为 200 时,表示健康状态,否则为非健康状态。
+- sentinelEnable 是否启用 sentinel 限流功能
+- sentinelBlockPage 若该路由被限流后,网页自动跳转到哪个网址
+- sentinelBlockJsonMap 若该路由被限流后,自动渲染的 jsonMap,若 sentinelBlockPage 已经配置,则 sentinelBlockJsonMap 配置无效
+- proxyReadTimeout 发生路由后,默认的请求超时时间,默认为 10 秒
+- proxyConnectTimeout 发生路由后,默认的连接超时时间,默认为 5 秒
+- proxyContentType 发生路由后,返回给浏览器的 http-content-type,默认为:text/html;charset=utf-8
+- interceptors 网关拦截器,一般用于进行鉴权等功能,配置类名,多个拦截器用英文逗号隔开,拦截器必须实现 GatewayInterceptor 接口
+- loadBalanceStrategy 负载均衡策略,当配置了多个 uri 的时候,可以通过此策略对 uri 进行获取
+
+> 注意:开启健康检查后,当所有的目标地址都不健康的时候,会渲染 "none health url in gateway" 的错误信息。
+> 我们可以通过 `JbootGatewayManager.me().setNoneHealthUrlErrorRender()` 来自定义渲染功能。
+
+## Path 路由
+
+Path 路由一般是最常用的路由之一,是根据域名之后的路径进行路由的,Jboot 对 Path 路由提供了 4 中方式:
+
+**1、pathEquals**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.pathEquals = /user
+```
+
+当用户访问 www.xxx.com/user 的时候,自动路由到 `http://youdomain:8080/user`,但是当用户请求 `www.xxx.com/user/other` 的时候不会进行路由。
+
+**2、pathContains**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.pathContains = /user
+```
+
+当 path 中,只要存在 `/user` 就会匹配到该路由,比如 `www.xxx.com/user/other` 或者 `www.xxx.com/other/user/xxx` 都会匹配到。
+
+
+**3、pathStartsWith**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.pathStartsWith = /user
+```
+
+当 path 中,只要以 `/user` 开头就会匹配到该路由,比如 `www.xxx.com/user/other` ,但是 `www.xxx.com/other/user/xxx` 不会匹配到,因为它是以 `/other` 开头的。
+
+**4、pathEndsWith**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.pathEndsWith = /user
+```
+
+当 path 中,只要以 `/user` 结束就会匹配到该路由,比如 `www.xxx.com/other/user` ,但是 `www.xxx.com/user/other` 不会匹配到,因为它是以 `/other` 结束的。
+
+
+## Host 路由
+
+Host 路由是根据域名进行路由的,Jboot 对 Host 路由提供了 4 中方式:
+
+**1、hostEquals**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.hostEquals = xxx.xxx.com
+```
+
+当用户访问 xxx.xxx.com/user/xx 的时候,自动路由到 `http://youdomain:8080/user/xx`,但是当用户请求 `www.xxx.com/user/xxx` 的时候不会进行路由。
+
+**2、hostContains**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.hostContains = xxx.xxx.com
+```
+
+在 Host 中,只要存在 `xxx.xxx.com` 就会匹配到该路由,比如 `aaa.bbb.xxx.xxx.com/user/other` 会自动路由到 `http://youdomain:8080/user/other`。
+
+
+**3、hostStartsWith**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.hostStartsWith = xxx
+```
+
+在 Host 中,只要以 `xxx` 开头就会匹配到该路由,比如 `xxx.xxx.com/user/other` ,但是 `www.xxx.com/other/user/xxx` 不会匹配到,因为它的域名(host)是以 `xxx` 开头的。
+
+**4、hostEndsWith**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.hostEndsWith = com
+```
+
+在 Host 中,只要以 `com` 结束就会匹配到该路由,比如 `www.xxx.com/other/user` ,但是 `www.xxx.org/user/other` 不会匹配到,因为它的域名是以 `org` 结束的。
+
+## Query 路由
+
+根据 get 请求的参数进行路由,注意 post 请求参数不会路由。
+
+
+**1、queryEquals**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.queryEquals = aaa:bbb
+```
+
+以上配置中,如果用户访问 `www.xxx.com/controller?aaa=bbb` 会自动路由到 `http://youdomain:8080/controller?aaa=bbb` ,但是如果用户访问 `www.xxx.com/controller?aaa=ccc`不会路由。
+
+**2、queryContains**
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.queryContains = aaa
+```
+
+以上配置中,如果用户访问 `www.xxx.com/controller?aaa=bbb` 会自动路由到 `http://youdomain:8080/controller?aaa=bbb` ,或者用户访问 `www.xxx.com/controller?aaa=ccc` 也会路由到 `http://youdomain:8080/controller?aaa=ccc`,因为 query 都包含了 `aaa=**` 的请求,但是如果用户访问 `www.xxx.com/controller?other=aaa`不会路由。
+
+
+
+## 多个 Gateway 配置
+
+```properties
+jboot.gateway.aaa.name = name
+jboot.gateway.aaa.uri = http://youdomain:8080
+jboot.gateway.aaa.enable = true
+jboot.gateway.aaa.sentinelEnable = false
+jboot.gateway.aaa.sentinelBlockPage = /block
+jboot.gateway.aaa.proxyReadTimeout = 10000
+jboot.gateway.aaa.proxyConnectTimeout = 5000
+jboot.gateway.aaa.proxyContentType = text/html;charset=utf-8
+jboot.gateway.aaa.interceptors = com.xxx.Interceptor1,com.xxx.Interceptor2
+jboot.gateway.aaa.pathEquals = /path
+jboot.gateway.aaa.pathContains = /path
+jboot.gateway.aaa.pathStartsWith = /path
+jboot.gateway.aaa.pathEndswith = /path
+jboot.gateway.aaa.hostEquals = xxx.com
+jboot.gateway.aaa.hostContains = xxx.com
+jboot.gateway.aaa.hostStartsWith = xxx.com
+jboot.gateway.aaa.hostEndswith = xxx.com
+jboot.gateway.aaa.queryEquals = aa:bb,cc:dd
+jboot.gateway.aaa.queryContains = aa,bb
+
+
+jboot.gateway.bbb.name = name
+jboot.gateway.bbb.uri = http://youdomain:8080
+jboot.gateway.bbb.enable = true
+jboot.gateway.bbb.sentinelEnable = false
+jboot.gateway.bbb.sentinelBlockPage = /block
+jboot.gateway.bbb.proxyReadTimeout = 10000
+jboot.gateway.bbb.proxyConnectTimeout = 5000
+jboot.gateway.bbb.proxyContentType = text/html;charset=utf-8
+jboot.gateway.bbb.interceptors = com.xxx.Interceptor1,com.xxx.Interceptor2
+jboot.gateway.bbb.pathEquals = /path
+jboot.gateway.bbb.pathContains = /path
+jboot.gateway.bbb.pathStartsWith = /path
+jboot.gateway.bbb.pathEndswith = /path
+jboot.gateway.bbb.hostEquals = xxx.com
+jboot.gateway.bbb.hostContains = xxx.com
+jboot.gateway.bbb.hostStartsWith = xxx.com
+jboot.gateway.bbb.hostEndswith = xxx.com
+jboot.gateway.bbb.queryEquals = aa:bb,cc:dd
+jboot.gateway.bbb.queryContains = aa,bb
+
+
+jboot.gateway.xxx.name = name
+jboot.gateway.xxx.uri = http://youdomain:8080
+jboot.gateway.xxx.enable = true
+jboot.gateway.xxx.sentinelEnable = false
+jboot.gateway.xxx.sentinelBlockPage = /block
+jboot.gateway.xxx.proxyReadTimeout = 10000
+jboot.gateway.xxx.proxyConnectTimeout = 5000
+jboot.gateway.xxx.proxyContentType = text/html;charset=utf-8
+jboot.gateway.xxx.interceptors = com.xxx.Interceptor1,com.xxx.Interceptor2
+jboot.gateway.xxx.pathEquals = /path
+jboot.gateway.xxx.pathContains = /path
+jboot.gateway.xxx.pathStartsWith = /path
+jboot.gateway.xxx.pathEndswith = /path
+jboot.gateway.xxx.hostEquals = xxx.com
+jboot.gateway.xxx.hostContains = xxx.com
+jboot.gateway.xxx.hostStartsWith = xxx.com
+jboot.gateway.xxx.hostEndswith = xxx.com
+jboot.gateway.xxx.queryEquals = aa:bb,cc:dd
+jboot.gateway.xxx.queryContains = aa,bb
+```
+
+## 服务发现
+
+Jboot Gateway 功能通过 Nacos(可以通过 SPI 进行扩展其他事项方式) 实现了自动发现服务。
+
+相关代码示例可以参考:
+
+https://gitee.com/JbootProjects/jboot/tree/master/simples/gateway
+
+
+使用方法如下:
+
+1、新增 nacos 依赖(Gateway 端和服务端都需要)
+
+```xml
+
+ com.alibaba.nacos
+ nacos-client
+ 版本号
+
+```
+
+2、启动 Nacos
+
+参考文档:
+
+https://nacos.io/zh-cn/docs/quick-start.html
+
+
+3、在 Gateway 网关端添加如下配置:
+
+```properties
+jboot.gateway.name = myName
+jboot.gateway.enable = true
+jboot.gateway.pathStartsWith = /
+
+
+jboot.gateway.discovery.enable = true
+
+#若配置其他,则自行通过 SPI 进行扩展
+jboot.gateway.discovery.type = nacos
+
+#默认值为:DEFAULT_GROUP
+jboot.gateway.discovery.group =
+
+jboot.gateway.discovery.nacos.serverAddr = 127.0.0.1:8848
+```
+
+更多的 nacos 配置如下:
+```properties
+jboot.gateway.discovery.nacos.isUseCloudNamespaceParsing = xxx
+jboot.gateway.discovery.nacos.isUseEndpointParsingRule = xxx
+jboot.gateway.discovery.nacos.endpoint = xxx
+jboot.gateway.discovery.nacos.endpointPort = xxx
+jboot.gateway.discovery.nacos.namespace = xxx
+jboot.gateway.discovery.nacos.username = xxx
+jboot.gateway.discovery.nacos.password = xxx
+jboot.gateway.discovery.nacos.accessKey = xxx
+jboot.gateway.discovery.nacos.secretKey = xxx
+jboot.gateway.discovery.nacos.ramRoleName = xxx
+jboot.gateway.discovery.nacos.contextPath = xxx
+jboot.gateway.discovery.nacos.clusterName = xxx
+jboot.gateway.discovery.nacos.encode = xxx
+jboot.gateway.discovery.nacos.configLongPollTimeout = xxx
+jboot.gateway.discovery.nacos.configRetryTime = xxx
+jboot.gateway.discovery.nacos.maxRetry = xxx
+jboot.gateway.discovery.nacos.enableRemoteSyncConfig = xxx
+```
+
+4、在 服务端 添加如下配置
+
+```properties
+jboot.gateway.discovery.enable = true
+
+#若配置其他,则自行通过 SPI 进行扩展
+jboot.gateway.discovery.type = nacos
+
+#默认值为:DEFAULT_GROUP,这个值必须和 gateway 配置的一致
+jboot.gateway.discovery.group =
+
+## 注意:这个配置的 myName 必须和 Gateway 里的 'jboot.gateway.name = myName' 中的 myName 一样
+jboot.gateway.instance.name = myName
+
+#不配置默认为 http,可以配置为 https
+jboot.gateway.instance.uriScheme = http
+
+#不配置默认为当前服务器的IP地址,可以获取错误
+jboot.gateway.instance.uriHost =
+
+#默认为 undertow.port 的配置
+jboot.gateway.instance.uriPort = http
+
+jboot.gateway.instance.uriPath = /user/aaa
+
+jboot.gateway.discovery.nacos.serverAddr = 127.0.0.1:8848
+```
+
+更多的 nacos 配置如下:
+```properties
+jboot.gateway.discovery.nacos.isUseCloudNamespaceParsing = xxx
+jboot.gateway.discovery.nacos.isUseEndpointParsingRule = xxx
+jboot.gateway.discovery.nacos.endpoint = xxx
+jboot.gateway.discovery.nacos.endpointPort = xxx
+jboot.gateway.discovery.nacos.namespace = xxx
+jboot.gateway.discovery.nacos.username = xxx
+jboot.gateway.discovery.nacos.password = xxx
+jboot.gateway.discovery.nacos.accessKey = xxx
+jboot.gateway.discovery.nacos.secretKey = xxx
+jboot.gateway.discovery.nacos.ramRoleName = xxx
+jboot.gateway.discovery.nacos.contextPath = xxx
+jboot.gateway.discovery.nacos.clusterName = xxx
+jboot.gateway.discovery.nacos.encode = xxx
+jboot.gateway.discovery.nacos.configLongPollTimeout = xxx
+jboot.gateway.discovery.nacos.configRetryTime = xxx
+jboot.gateway.discovery.nacos.maxRetry = xxx
+jboot.gateway.discovery.nacos.enableRemoteSyncConfig = xxx
+```
+
+## 注意事项
+当配置中,如果一个内容存在多个值的时候,需要用英文逗号(,)隔开。
+
+比如:
+
+```properties
+jboot.gateway.name = name
+jboot.gateway.uri = http://youdomain:8080
+jboot.gateway.enable = true
+
+jboot.gateway.pathContains = /user,/article
+```
+
+当 path 中,只要存在 `/user` 或者 存在 `/article` 都会匹配到该路由,比如 `www.xxx.com/user/xxx` 或者 `www.xxx.com/article/xxx` 都会匹配到。
+
+其他同理。
\ No newline at end of file
diff --git a/doc/docs/hotload.md b/doc/docs/hotload.md
index adea77222641c7145cbd675c4141f21415d6e4d5..3a3777342b2959921684f06e686943c0e7ac1cdf 100644
--- a/doc/docs/hotload.md
+++ b/doc/docs/hotload.md
@@ -5,15 +5,15 @@
## 目录
- 描述
-- Maven 多模块( module )下资源文件热加载
+- Maven 多模块( module )下资源文件热加载
- 常见错误
## 描述
-在 Jboot 开发模式下,默认开启热加载功能,若要关闭热加载功能,可以添加配置 `undertow.devMode = false` , 或者通过 `jboot.app.mode = product` 配置修改当前应用为生产环境。
+在 Jboot 开发模式下,可以添加配置 `undertow.devMode = true` 来开启热加载功能。
-## Maven 多模块( module )下资源文件热加载
+## Maven 多模块( module )下资源文件热加载
在 Jboot 中,内置了一个类叫 `JbootResourceLoader`,只有在 dev 模式下才生效。
@@ -21,7 +21,7 @@
倘若你的资源文件不在 `src/main/webapp` 目录,则需要配置 `jboot.app.resourcePathName = myResourcePath` ,那么,当 Jboot 启动的时候则会启动去监控各个模块的 `src/main/myResourcePath` 目录,并自动同步到 `classpath:myResourcePath` 下。
-默认是 `src/main/webapp`,若你的资源文件在 `src/main/webapp` 目录下,不需要任何配置即可以实现资源文件的热加载功能。
+默认是 `src/main/webapp`,若你的资源文件在 `src/main/webapp` 目录下不需要任何配置即可以实现资源文件的热加载功能。
## 常见错误
@@ -36,4 +36,4 @@ undertow.hotSwapClassPrefix = xxx1.com, xxx2.com
- **错误2:没有热加载**
在 idea 开发工具中,可能会出现未正确进行热加载的情况,一般是没有配置 idea 的自动编译功能。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/doc/docs/imgs/jboot-wechat-group.png b/doc/docs/imgs/jboot-wechat-group.png
deleted file mode 100644
index 3d46050d5d6276b97877067ed4e33d62ab57c764..0000000000000000000000000000000000000000
Binary files a/doc/docs/imgs/jboot-wechat-group.png and /dev/null differ
diff --git a/doc/docs/install.md b/doc/docs/install.md
index eab83655a318aa39ef9896361fa65e3ce90f73f8..fa52c91def1aecd869cd111856f02da459ef79a1 100644
--- a/doc/docs/install.md
+++ b/doc/docs/install.md
@@ -9,7 +9,7 @@
io.jboot
jboot
- 3.0.1
+ 4.1.0
```
diff --git a/doc/docs/jfinalConfig.md b/doc/docs/jfinalConfig.md
index ff66625118facff2f8c5a31edb59e756931ca3e1..eb5ff5d0c362a6d2df6765ba41e99ec8c4d48745 100644
--- a/doc/docs/jfinalConfig.md
+++ b/doc/docs/jfinalConfig.md
@@ -1,6 +1,8 @@
-# 如何在Jboot中添加自己的 JFianlConfig
+# 如何在Jboot中添加自己的 JFinalConfig
-凡是开发过 JFinal 的同学,都知道 JFinalConfig 是 JFinal 的核心配置,详情: https://www.jfinal.com/doc/2-1 ,其内容如下:
+[[toc]]
+
+JFinalConfig 是 JFinal 的核心配置,详情: https://www.jfinal.com/doc/2-1 ,其内容如下:
```java
public class DemoConfig extends JFinalConfig {
@@ -14,7 +16,7 @@ public class DemoConfig extends JFinalConfig {
```
默认情况下,我们不需要对 JFinal 进行任何的配置,因为 Jboot 已经对 JFinal 进行了默认的配置,同时,Controller 等的配置完全是通过注解
-@RequestMapping 来配置了,数据库也只是在 jboot.properties 里添加就可以。
+`@RequestMapping` 来配置了,数据库也只是在 jboot.properties 里添加就可以。
但是可能在某些特殊情况下,我们对 JFinal 进行自己特殊的配置,如何来做呢?
@@ -59,12 +61,6 @@ public class JbootAppListenerBase implements JbootAppListener {
//对应 JFinalConfig 的 configInterceptor
}
- @Override
- public void onFixedInterceptorConfig(FixedInterceptors fixedInterceptors) {
- //FixedInterceptor 类似 Interceptor,
- // 但是 FixedInterceptor 不会被注解 @Clear 清除
- }
-
@Override
public void onHandlerConfig(JfinalHandlers handlers) {
//对应 JFinalConfig 的 configHandler
diff --git a/doc/docs/json.md b/doc/docs/json.md
new file mode 100644
index 0000000000000000000000000000000000000000..c037ecd477233e04be15e7377dcb1c27d75980d6
--- /dev/null
+++ b/doc/docs/json.md
@@ -0,0 +1,554 @@
+# Json 配置
+
+## 接收 Json
+
+### @JsonBody
+在 Controller 中,我们可以通过给参数添加 @JsonBody 注解来把客户端传入的 Json 数据,转换为我们需要的参数。
+
+#### @JsonBody 接收 Bean
+
+我们定义的 Bean 如下:
+
+```java
+public class MyBean {
+ private String id;
+ private int age;
+ private BigInteger amount;
+
+ //getter setter
+}
+```
+
+示例1:假设前端传入的 Json 数据内容如下:
+
+
+```json
+{
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+```
+
+在 Controller 中,我们只需要编写如下内容就可以正常接收:
+
+```java
+public void bean(@JsonBody() MyBean bean) {
+ System.out.println("bean--->" + bean);
+ renderText("ok");
+}
+```
+
+
+示例2:假设我们接收的 Bean 在 Json 实体里,例如:
+```json
+{ "aaa":{
+ "bbb":{
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ }
+ }
+```
+
+ 在 Controller 中,我们只需要编写如下内容就可以正常接收:
+
+```java
+public void bean(@JsonBody("aaa.bbb") MyBean bean) {
+ System.out.println("bean--->" + bean);
+ renderText("ok");
+}
+```
+
+#### @JsonBody 接收 Map
+
+ 接收 Map 和 Bean 是一样的,只是参数不同。
+
+示例1:假设前端传入的 Json 数据内容如下:
+
+
+```json
+{
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+```
+
+在 Controller 中,我们只需要编写如下内容就可以正常接收:
+
+```java
+public void bean(@JsonBody() Map bean) {
+ System.out.println("bean--->" + bean);
+ renderText("ok");
+}
+```
+
+
+示例2:假设我们接收的 Map 在 Json 实体里,例如:
+```json
+{ "aaa":{
+ "bbb":{
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ }
+ }
+```
+ Controller 代码如下:
+```java
+public void bean(@JsonBody("aaa.bbb") Map bean) {
+ System.out.println("bean--->" + bean);
+ renderText("ok");
+}
+```
+
+#### @JsonBody 接收 集合(List、Set、Queue、Vector、Stack、Deque 和 数组)
+
+示例1:前端传入的内容如下
+
+```json
+[1,2,3]
+```
+
+如下的方法,都可以正常接收数据:
+
+```java
+//通过 int[] 数组来接收
+public void method1(@JsonBody() int[] beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+ //通过 String[] 数组来接收
+public void method2(@JsonBody() String[] beans) {
+ ystem.out.println("beans--->" + beans);
+ enderText("ok");
+}
+
+//通过 List 来接收
+public void method3(@JsonBody() List beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+//通过 Set 来接收
+public void method4(@JsonBody() Set beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+//通过 List 指定泛型 Integer 来接收
+public void method5(@JsonBody() List beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+//通过 Set 指定泛型 Integer 来接收
+public void method6(@JsonBody() Set beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+ //通过 List 指定泛型 String 来接收
+public void method7(@JsonBody() List beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+
+ //通过 Set 指定泛型 String 来接收
+public void method8(@JsonBody() Set beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+```
+
+当然,我们可以把 List 或者 Set 修改为 Queue、Vector、Stack、Deque 等其他数据类型。
+
+如果我们要接收的数组包括在 Json 实体里,例如:
+
+```json
+{
+"aaa":{
+ "bbb":[1,2,3]
+ }
+}
+```
+
+只需要在 @JsonBody 添加对应的前缀即可,比如:
+
+```java
+public void method1(@JsonBody("aaa.bbb") int[] beans) {
+ System.out.println("beans--->" + beans);
+ renderText("ok");
+}
+```
+
+ 示例2,接收 Bean 数组:
+
+比如前端传入的是:
+
+```json
+{
+"aaa":{
+ "bbb":[
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ },
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ ]
+ }
+}
+
+```
+
+Controller 的代码如下:
+
+```java
+public void list(@JsonBody("aaa.bbb") List list) {
+ System.out.println("list--->" + list);
+ renderText("ok");
+ }
+```
+
+或者
+
+```java
+public void set(@JsonBody("aaa.bbb") Set" + beans);
+ renderText("ok");
+}
+```
+或者
+
+```java
+public void array(@JsonBody("aaa.bbb") MyBean[] beans) {
+ System.out.println("array--->" + beans);
+ renderText("ok");
+}
+```
+
+### 通过 @JsonBody 接收基本数据
+
+假设客户端(前端)传入的数据内容如下,我们想获取 `id` 的值。
+
+```json
+{ "aaa":{
+ "bbb":{
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ }
+ }
+```
+
+Controller 代码如下:
+
+```java
+ public void id(@JsonBody("aaa.bbb.id") String id) {
+ System.out.println("id--->" + id);
+ renderText("ok");
+ }
+```
+
+或者我们接收 `age` 参数,内容 Controller 代码如下:
+
+```java
+public void age(@JsonBody("aaa.bbb.age") int age) {
+ System.out.println("age--->" + age);
+ renderText("ok");
+ }
+```
+ 此处要注意,因为 `age` 在方法中定义的是 `int` 类型,当前端没有传入任何值得时候,`age` 得到的值为: `0`。所以,如果我们想接收基本数据类型的值,建议定义为其封装类型,比如 int 定义为 Integer,long 定位为 Long 等。
+
+
+### @JsonBody 高级特性:自动拆装
+
+示例1:假设前端传入的内容如下:
+
+```json
+{
+"aaa":{
+ "bbb":[
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ },
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ ]
+ }
+}
+```
+
+我们想接收到所有 `id` 值,并自动转换为一个 `String[]` 数组、或者 `List `等类型,接收代码如下:
+
+```java
+public void array(@JsonBody("aaa.bbb[id]") String[] ids) {
+ System.out.println("array--->" + ids);
+ renderText("ok");
+}
+```
+
+或者
+```java
+public void array(@JsonBody("aaa.bbb[id]") List ids) {
+ System.out.println("array--->" + ids);
+ renderText("ok");
+}
+```
+
+示例2,如果我们想获取数组里的某个值,比如前端传入的数据内容如下:
+
+```json
+{
+"aaa":{
+ "bbb":[
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ },
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ ]
+ }
+}
+```
+
+我们想获取 第一个 `age` 的值,接收数据内容如下:
+
+```java
+public void age(@JsonBody("aaa.bbb[0].age") long age) {
+ renderText("intInArray--->" + age);
+}
+```
+
+示例3:
+假设前度传入的 Json 内容如下:
+
+```json
+{
+ "aaa":{
+ "bbb":[
+ {
+ "attr1":"abc",
+ "beans":[
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ },
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ ]
+ },
+ {
+ "attr2":"abc"
+ }
+ ]
+ }
+}
+```
+我们想获取 beans 的值,Controller 内容如下:
+
+```java
+public void array(@JsonBody("aaa.bbb[0].beans") MyBean[] beans) {
+ System.out.println("array--->" + JsonKit.toJson(beans));
+ renderText("ok");
+}
+```
+
+如果我们想获取 `beans` 下的所有 `id` 值,Controller 内容如下:
+
+```java
+public void array(@JsonBody("aaa.bbb[0].beans[id]") String[] ids) {
+ System.out.println("array--->" + JsonKit.toJson(ids));
+ renderText("ok");
+}
+```
+
+### 直接通过 Controller 接收 Json
+
+在 Controller 中,我们如果不通过 `@JsonBody` 接收 Json 数据,也是没问题的。
+
+例如:前端传入的 Json 内容如下:
+
+```json
+{
+ "aaa":{
+ "bbb":[
+ {
+ "attr1":"abc",
+ "beans":[
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ },
+ {
+ "id":"abc",
+ "age":17,
+ "amount":123
+ }
+ ]
+ },
+ {
+ "attr2":"abc"
+ }
+ ]
+ }
+}
+```
+
+我们想获取 beans 的值,Controller 内容如下:
+
+```java
+public void array() {
+ MyBean[] beans = getRawObject(MyBean[].class,"aaa.bbb[0].beans");
+ System.out.println("array--->" + JsonKit.toJson(beans));
+ renderText("ok");
+}
+```
+
+此处要注意,如果我们想获取一个泛型 `List`,需要使用如下方法:
+
+```java
+public void array() {
+ List beans = getRawObject(new TypeDef>(){},"aaa.bbb[0].beans");
+ System.out.println("array--->" + JsonKit.toJson(beans));
+ renderText("ok");
+}
+```
+
+或者
+
+```java
+public void array() {
+ Set beans = getRawObject(new TypeDef>(){},"aaa.bbb[0].beans");
+ System.out.println("array--->" + JsonKit.toJson(beans));
+ renderText("ok");
+}
+```
+
+如果我们想获取 `beans` 下的所有 `id` 值,Controller 内容如下:
+
+
+```java
+public void array(@JsonBody() {
+ String[] ids = getRawObject(String[].class,"aaa.bbb[0].beans[id]");
+ System.out.println("array--->" + JsonKit.toJson(ids));
+ renderText("ok");
+}
+```
+
+或者
+```java
+public void array() {
+ List ids = getRawObject(new TypeDef>(){},"aaa.bbb[0].beans[id]");
+ System.out.println("array--->" + JsonKit.toJson(ids));
+ renderText("ok");
+}
+```
+
+或者
+
+```java
+public void array() {
+ Set ids = getRawObject(new TypeDef>(){},"aaa.bbb[0].beans[id]");
+ System.out.println("array--->" + JsonKit.toJson(ids));
+ renderText("ok");
+}
+```
+
+## 输出 Json
+
+
+### jboot v3.5.1(包括 3.5.1) 之后的配置
+
+```
+#是否启用大写转换,比如 user_age 自动转为为 UserAge,只对 Model 生效,默认值为 true
+jboot.json.camelCaseJsonStyleEnable = true
+
+#是否转换任何地方,比如 map.put("my_field") 默认输出为 myField,默认值为 false
+jboot.json.camelCaseToLowerCaseAnyway = false
+
+#是否跳过 null 值输出,map.put("key",null),则 key 值不输,默认值为 true
+jboot.json.skipNullValueField = true
+
+#配置输出的时间格式,默认值为 yyyy-MM-dd HH:mm:ss
+jboot.json.timestampPattern = "yyyy-MM-dd HH:mm:ss"
+
+# 是否跳过 model 的 attr,只用 bean 的 getter 来渲染
+jboot.json.skipModelAttrs = false
+
+# 是否值跳过 bean 的 getter,只用 model 的 attr 渲染
+jboot.json.skipBeanGetters =false
+```
+
+### jboot v3.5.1 之前的配置
+
+```
+#是否启用大写转换,比如 user_age 自动转为为 UserAge
+jboot.web.camelCaseJsonStyleEnable = true
+
+#是否转换任何地方,比如 map.put("my_field") 默认输出为 myField
+jboot.web.camelCaseToLowerCaseAnyway = false
+
+#配置输出的时间格式
+jboot.web.jsonTimestampPattern
+```
+
+### 通过 Java 代码配置
+
+更多的配置可以通过 JFinalJsonKit 工具类来进行配置。
+
+### 其他
+
+- 1、通过 @JsonIgnore 注解可以忽略某个字段,比如
+
+```java
+public class User{
+
+ @JsonIgnore
+ public String getPassword(){
+ return Supper.getPassword();
+ }
+}
+```
+
+以上代码可以忽略 password 的输出。
+
+- 2、支持 FastJson 的 JsonField 的配置
+
+```java
+public class User {
+
+ @JSONField(name = "sex")
+ public String getSexString(){
+ return "男";
+ }
+}
+```
+
+输出字段又 `sexString` 重新修改为 `sex`。
+
diff --git a/doc/docs/junit.md b/doc/docs/junit.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe345a9ecc5fcade739b2ecbd519eb99c3885e5f
--- /dev/null
+++ b/doc/docs/junit.md
@@ -0,0 +1,254 @@
+# Junit 单元测试
+
+## 目录
+- Junit4 单元测试
+- Junit5 单元测试
+- @TestConfig
+- @MockMethod
+- @MockClass
+
+## Junit 单元测试简介
+
+单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。
+
+JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
+
+
+目前市面上主要是使用 Junit4 和 Junit5 对 Java 程序进行单元测试。
+
+
+## Junit4 单元测试
+
+1、第一步,添加 junit4 的 maven 依赖
+
+```xml
+
+ junit
+ junit
+ 4.13.2
+ test
+
+```
+
+2、第二步,编写单元测试代码
+
+```java
+@RunWith(JbootRunner.class)
+public class MyAppTester {
+
+ private static MockMvc mvc = new MockMvc();
+
+ @Inject
+ private MyService myService;
+
+ @Test
+ public void test_url_aaa() {
+ MockMvcResult mvcResult = mvc.get("/aaa");
+
+ mvcResult.printResult()
+ .assertThat(result -> Assert.assertNotNull(result.getContent()))
+ .assertTrue(result -> result.getStatus() == 200);
+ }
+
+ @Test
+ public void test_url_bbb() {
+ MockMvcResult mvcResult = mvc.get("/bbb");
+
+ mvcResult.printResult()
+ .assertThat(result -> Assert.assertNotNull(result.getContent()))
+ .assertTrue(result -> result.getStatus() == 200);
+ }
+
+ @Test
+ public void test_my_service() {
+ Ret ret = myService.doSomeThing();
+ Assert.assertNotNull(ret);
+ //.....
+ }
+}
+```
+> 注意:Junit4 测试类必须添加 `@RunWith(JbootRunner.class)` 配置
+
+
+## Junit5 单元测试
+
+1、第一步,添加 junit4 的 maven 依赖
+
+```xml
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.7.2
+ test
+
+```
+
+2、第二步,编写单元测试代码
+
+```java
+@ExtendWith(JbootExtension.class)
+public class MyAppTester {
+
+ private static MockMvc mvc = new MockMvc();
+
+ @Inject
+ private MyService myService;
+
+ @Test
+ public void test_url_aaa() {
+ MockMvcResult mvcResult = mvc.get("/aaa");
+
+ mvcResult.printResult()
+ .assertThat(result -> Assertions.assertNotNull(result.getContent()))
+ .assertTrue(result -> result.getStatus() == 200);
+ }
+
+ @Test
+ public void test_url_bbb() {
+ MockMvcResult mvcResult = mvc.get("/bbb");
+
+ mvcResult.printResult()
+ .assertThat(result -> Assertions.assertNotNull(result.getContent()))
+ .assertTrue(result -> result.getStatus() == 200);
+ }
+
+ @Test
+ public void test_my_service() {
+ Ret ret = myService.doSomeThing();
+ Assertions.assertNotNull(ret);
+ //.....
+ }
+}
+```
+> 注意:Junit5 测试类必须添加 `@ExtendWith(JbootExtension.class)` 配置
+
+
+## @TestConfig
+
+在测试的过程中,Jboot 默认的 webRootPath 是 `target/classes/webapp` 目录,而 classPath 的目录是 `target/classes`
+目录。
+
+如果我们需要修改此目录,则需要在测试类中添加 @TestConfig 注解,对 webRootPath 或 classPath 进行配置。
+
+例如:
+
+```java
+@RunWith(JbootRunner.class)
+@TestConfig(webRootPath = "your-path",classPath = "your-path")
+public class MyAppTester {
+
+ private static MockMvc mvc = new MockMvc();
+
+
+ @Test
+ public void test_url_aaa() {
+ MockMvcResult mvcResult = mvc.get("/aaa");
+
+ //your code ...
+ }
+
+
+}
+```
+
+`@TestConfig(webRootPath = "your-path",classPath = "your-path")` 里的配置路径,可以是绝对路径或相对路径,
+若是相对路径,则是相对 `target/test-classes` 目录的路径。
+
+## @MockMethod
+
+Jboot 提供了 `@MockMethod` 注解,方便对 AOP 管理的类里的方法(method)进行 Mock 操作。
+
+例如:
+```java
+@RunWith(JbootRunner.class)
+@TestConfig(autoMockInterface = true)
+public class OptionApiControllerTest {
+
+ private static final MockMvc mvc = new MockMvc();
+
+ @Test
+ public void query() {
+ mvc.get("/api/option/query?key=myKey").printResult();
+ }
+
+
+ @MockMethod(targetClass = JPressCoreInitializer.class)
+ public void onHandlerConfig(JfinalHandlers handlers) {
+ handlers.add(new JPressHandler());
+ }
+
+
+ @MockMethod(targetClass = UtmService.class)
+ public void doRecord(Utm utm){
+ System.out.println(">>>>>>>>>doRecord: " + utm);
+ }
+
+
+ @MockMethod(targetClass = WebInitializer.class,targetMethod = "onEngineConfig")
+ public void mock_on_engine_config(Engine engine){
+ System.out.println(">>>>>>>>>onEngineConfig: " + engine);
+ }
+}
+```
+
+在以上的代码中,有几个关键的地方
+- `@TestConfig(autoMockInterface = true)`,表示当我们测试 `query()` 方法的时候,可能会遇到一些注入进来的接口,但是可能没有实现类(或者说实现类在别的 Maven Module,并没有依赖进来),但是保证不出错。
+当调用接口方法时,等同于什么都不做,若有返回值,则返回 `null`。
+- `@MockMethod(targetClass = JPressCoreInitializer.class)` 表示复写 `JPressCoreInitializer` 类的 `onHandlerConfig` 方法。
+- `@MockMethod(targetClass = UtmService.class)` 表示复写 `UtmService` 类的 `doRecord` 方法。
+- `@MockMethod(targetClass = WebInitializer.class,targetMethod = "onEngineConfig")` 表示复写 `WebInitializer` 类的 `onEngineConfig` 方法。
+
+> 注意:
+>
+> 1、在以上代码中,`mock_on_engine_config` 方法的参数必须和 `WebInitializer.onEngineConfig` 里的参数一样,
+> 或者 `mock_on_engine_config` 可以多出一个 `targetClass` 的对象参数,例如:mock_on_engine_config(WebInitializer webInitializer,Engine engine)
+>
+> 2、`@MockMethod` 的优先级高于 `@TestConfig(autoMockInterface = true)` 的配置。
+
+
+## @MockClass
+
+Jboot 提供了 `@MockClass` 注解,方便对 AOP 管理的类(Class)进行 Mock 操作。
+
+例如,如下的代码是请求了 `/api/article/detail` 这个 API 接口。
+
+```java
+public class ArticleApiControllerTest{
+
+ static final MockMvc mvc = new MockMvc();
+
+ @Test
+ public void detail() {
+ mvc.get("/api/article/detail?id=1").printResult();
+ }
+}
+```
+
+假设这个 API 接口里的 `Controller` 通过 `ArticleService` 进行进步一查询数据。除了我们可以通过 `@MockMethod` 对 `ArticleService` 的方法进行 Mock,
+也编写一个类,实现 `ArticleService` 接口,并通过 `@MockClass` 添加在实现的类上。
+
+```java
+@MockClass
+public class ArticleServiceMock implements ArticleService {
+
+ @Override
+ public Article findById(Object id) {
+ Article article = new Article();
+ article.setId((Long) id);
+ article.setStatus(Article.STATUS_NORMAL);
+ return article;
+ }
+
+}
+```
+
+此时,我们在测试的时候,当 `Controller` 调用了 `ArticleService.findById(id)` 就会自动调用了 `ArticleServiceMock` 里的 `findById` 方法。
+
+相对 `@MockMethod` 而言,`@MockClass` 有几个好处:
+
+- 1、`@MockClass` 注解的类是去实现某个接口的,ide 等工具会自动帮我们生成方法,不易出错。
+- 2、当 Mock 很多个方法的时候,可以写到这个类里,使得测试代码更加简洁
+- 3、在有多个测试类的时候,多个测试类可以共用相同的 Mock 代码,减少代码量
+
+
+> 注意:如果 `@MockClass` 和 `@MockMethod` 同时对一个方法进行 Mock,`@MockMethod` 的优先级高于 `@MockClass`。
\ No newline at end of file
diff --git a/doc/docs/jwt.md b/doc/docs/jwt.md
index aca24a393012538a80bc45a75ecbb4ec15c3e7ce..0be927a2fb9d28def9c0ff4c1640974ab2d14310 100644
--- a/doc/docs/jwt.md
+++ b/doc/docs/jwt.md
@@ -12,27 +12,44 @@ JWT 是 Json web token 的简称, 是为了在网络应用环境间传递声明
## JWT 配置
-- jboot.web.jwt.httpHeaderName:配置JWT的http头的key,默认为 `Jwt`
-- jboot.web.jwt.secret:配置JWT的密钥
-- jboot.web.jwt.validityPeriod:配置JWT的过期时间,默认不过期
+在使用 JWT 之前,我们需要对 JWT 进行一些必要的配置。
+
+- jboot.web.jwt.httpHeaderName:配置 JWT 的 http 头的 key,默认为 `Jwt`,可以不配置。
+- jboot.web.jwt.secret:配置 JWT 的密钥,必须配置,否则使用 jwt 会抛出异常或给出警告。
+- jboot.web.jwt.validityPeriod:配置 JWT 的过期时间,默认永不不过期。
## JWT 使用
-在 `JbootController` 中,新增了如下几个用于操作 JWT 的方法:
+在 `JbootController` 中,新增了如下几个用于操作 JWT 的方法,在使用 Jwt 之前,需要在使用 Jwt 的 Controller
+里添加注解 `@EnableJwt` ,才能够正常的生成和刷新 Jwt 。当有很多个 Controller 都使用 Jwt 的话,可以直接 创建
+一个 BaseController,然后在 BaseController 里添加注解 `@EnableJwt`。
+
- setJwtAttr():设置 jwt 的 key 和 value
- setJwtMap():把整个 map的key和value 设置到 jwt
- getJwtAttr():获取 已经设置进去的 jwt 信息
- getJwtAttrs():获取 所有已经设置进去的 jwt 信息
-- getJwtPara():获取客户端传进来的 jwt 信息,若 jwt 超时或者不被信任,那么获取到的内容为null
+- getJwtPara():获取客户端传进来的 jwt 信息,若 jwt 超时或者不被信任,那么获取到的内容为 null
+- getJwtParaToString()
+- getJwtParaToInt()
+- getJwtParaToLong()
+- getJwtParaToBigInteger()
+- getJwtParas()
## 注意事项
-在服务端通过 `setJwtAttr()` 方法设置 JWT 后,Http 的响应头会添加一个名称为 `Jwt` 的属性(可以通过 `jboot.web.jwt.httpHeaderName` 进行配置)。
+在服务端通过 `setJwtAttr()` 方法设置 JWT 后,Http 的响应头会添一个名称为 `Jwt` 的属性
+(可以通过 `jboot.web.jwt.httpHeaderName` 进行配置)。
+
+
+此时,客户端(浏览器、小程序、APP等)发现 Http 头有该属性后,需要客户端主动把该值存储起来。
+APP存储到数据库、浏览和小程序可以存储到 `localStorage` 等。
+当客户端进行 Http 请求的时候,需要在 Http 头添加下属性为 `Jwt`、值为之前存储数据 的请求头。
+
-此时,客户端(浏览器、小程序、APP等)发现 Http 头有该属性后,需要客户端主动把该值存储起来。APP存储到数据库、浏览和小程序可以存储到localStorage等,当客户端进行 Http 请求的时候,需要在 Http 头添加下属性为 `Jwt`、值为之前存储数据 的请求头。
+当客户端正确添加 `Jwt` 的 Http 请求头的时候,服务端可以通过 `getJwtPara()`
+方法获取到客户端传入的内容。
-当客户端正确添加 `Jwt` 的 Http 请求头的时候,服务端可以通过 `getJwtPara()` 方法获取到客户端传入的内容。
**注意:** 接收客户端传入的Jwt值是通过`getJwtPara()`方法,而不是 `getJwtAttr()`。
\ No newline at end of file
diff --git a/doc/docs/limit.md b/doc/docs/limit.md
index 3898f20aa095a5ad545e7cb83d0029a2b4afc4b7..546cc05fc81442ec45dd124d335eb93ab964857c 100644
--- a/doc/docs/limit.md
+++ b/doc/docs/limit.md
@@ -1,5 +1,7 @@
# 限流
+关于 Sentinel 限流功能,请点 [这里](./sentinel.md) 查看。
+
## 目录
- 限流的场景
@@ -31,25 +33,30 @@ Jboot 提供了两种方案:
在 `jboot.properties` 文件中定义如下:
```
jboot.limit.enable = true
-jboot.limit.rule = /user*:tb:1,io.jboot.aop*.get*(*):tb:1
+jboot.limit.rule = /user*:tb:1,/other*:iptb:1,io.jboot.aop*.get*(*):tb:1
+jboot.limit.ipWhitelist = 127.0.0.1
```
- jboot.limit.enable : 限流功能的开关
- jboot.limit.rule : 限流规则
+- jboot.limit.ipWhitelist : IP 白名单,多个 IP 用英文逗号隔开,此 IP 下的访问,将不受限流配置的影响
+
> 规则说明:
> - 1、可以配置多个规则,每个规则用英文逗号隔开
> - 2、规则分为三个部分,用冒号(:)隔开,分别是:资源、限流类型、限流参数值
-> - 3、限流的类型有2种、分别是:tb 和 cc。tb:TOKEN BUCKET(令牌桶),cc:CONCURRENCY(并发量)
+> - 3、限流的类型有4种、分别是:tb、cc、iptb 和 ipcc。tb:TOKEN BUCKET(令牌桶,每秒允许访问的数量),cc:CONCURRENCY(并发量),iptb:单个ip每秒允许访问的数量,ipcc:单个ip允许同时的并发量。
> - 4、星号(*)匹配任意字符,也可以是空字符。
-在以上配置中,配置了两个规则,分别是:
+在以上配置中,配置了三个规则,分别是:
- `/user*:tb:1`
+- `/other*:iptb:1`
- `io.jboot.aop*.get*(*):cc:1`
-第一个规则:匹配 `/user` 开头的所有url地址,每个 url 地址,1秒钟之内只允许访问1次。
-第二个规则:匹配 `io.jboot.aop` 开头的所有包名,并且 `get` 开头的所有任意参数的方法。并发量为 1。
+第一个规则:匹配 `/user` 开头的所有url地址,每个 url 地址,1 秒钟之内只允许访问 1 次。
+第二个规则:匹配 `/other` 开头的所有url地址,每个 ip 地址在 1 秒钟之内只允许访问 1 次。
+第三个规则:匹配 `io.jboot.aop` 开头的所有包名,并且 `get` 开头的所有任意参数的方法。并发量为 1。
**使用方案2:通过注解 `@EnableLimit`**
@@ -81,8 +88,9 @@ public class IndexController extends JbootController {
## 限流的实现
上文中提到,Jboot 提供了两种限流类型,他们分别是:
-- TOKEN BUCKET : 令牌桶
-- CONCURRENCY : 并发量
+
+- TOKEN BUCKET : 令牌桶,每秒允许访问的次数。
+- CONCURRENCY : 并发量,和时间无关。
TOKEN BUCKET 令牌桶,是通过 Google Guava 的 RateLimiter 来实现的。
diff --git a/doc/docs/metrics.md b/doc/docs/metrics.md
index 5ef5a117bdd1fb785afcf5027b3bffa696bbf242..cb0fe750de427a91acff29ded7dfcb7de0b5adfd 100644
--- a/doc/docs/metrics.md
+++ b/doc/docs/metrics.md
@@ -8,17 +8,17 @@ Jboot 内置了一套监控机制,可以用来监控 Controller、Service 等
- @EnableMetricMeter
- @EnableMetricTimer
-这些监控的数据,我们可以输出到 slf4j 日志,可以输入到网页的json,也可以通过配置直接把数据输出到 grafana,使用 grafana 面板来进行可视化的数据监控,如下图。
+这些监控的数据,我们可以输出到 slf4j 日志,可以输入到网页的 json,也可以通过配置直接把数据输出到 prometheus,然后使用 grafana 面板来进行可视化的数据监控,如下图。
-
+
## Metrics 输出到日志
这是最简单的一种方法,我们只需要在 jboot.properties 添加如下配置:
-```
-jboot.metric.url=-/metrics_admin
+```properties
+jboot.metric.enable=true
jboot.metric.reporter=slf4j
```
@@ -38,54 +38,100 @@ public class MetricsController extends JbootController {
此时,启动 jboot 应用后,当访问 `http://127.0.0.1:8080/` ,控制台(日志) 会定时输出 `http://127.0.0.1:8080/` 的并发量和访问次数。(默认情况下是1分钟输出一次日志)。
-同时,由于我们配置了 `jboot.metric.url=-/metrics_admin` ,我们可以通过 `http://127.0.0.1:8888/metrics_admin` 来查看 `index()` 这个方法的访问次数和并发量。
+如果我们需要通过网页来查看监控的 json 数据,可以添加配置
+```properties
+boot.metric.adminServletMapping = metrics.admin
+```
-## Metrics 输出到 grafana
+此时,我们可以通过 `http://127.0.0.1:8888/metrics.admin` 来查看 `index()` 这个方法的访问次数和并发量。
-grafana 并没有接收数据的能力,因此,jboot 的方案是先数据输出到 influxdb,在配置 grafana 来读取 influxdb 的数据。
-因此,在 grafana 正常显示 jboot 数据之前,先把 grafana 和 influxdb 启动起来。
+## Metrics 输出到 Grafana
-**启动 influxdb :**
+Grafana 是一个开源的度量分析与可视化套件。经常被用作基础设施的时间序列数据和应用程序分析的可视化,它在其他领域也被广泛的使用包括工业传感器、家庭自动化、天气和过程控制等。
-```
-docker run -d -p 8086:8086 -p 8083:8083 \
- -e INFLUXDB_ADMIN_ENABLED=true \
- -e INFLUXDB_DB=metricsDb \
- -e INFLUXDB_ADMIN_USER=admin \
- -e INFLUXDB_ADMIN_PASSWORD=123456 \
- -e INFLUXDB_USER=fuhai \
- -e INFLUXDB_USER_PASSWORD=123456 \
- influxdb
-```
+Grafana 支持许多不同的数据源,比如: Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 和 KairosDB 等。每个数据源都有一个特定的查询编辑器,该编辑器定制的特性和功能是公开的特定数据来源。
-**启动 grafana :**
+需要注意的是:Grafana 并没有接收数据的能力,因此,Jboot 的方案是先把数据输出到 Prometheus (或者 influxdb),再配置 Grafana 来读取 Prometheus (或者 influxdb) 的数据。
+
+因此,在 Grafana 正常显示 Jboot 数据之前,先把 Grafana 和 Prometheus 启动起来。
+
+**启动 Prometheus :**
+- 1、下载 Prometheus 到本地,下载地址:https://prometheus.io/download/
+- 2、进入到 Prometheus 的解压目录,修改 prometheus.yml 文件,内容如下:
+
+```yml
+# my global config
+global:
+ scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+ evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+ # scrape_timeout is set to the global default (10s).
+
+# Alertmanager configuration
+alerting:
+ alertmanagers:
+ - static_configs:
+ - targets:
+ # - alertmanager:9093
+
+# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
+rule_files:
+ # - "first_rules.yml"
+ # - "second_rules.yml"
+
+# A scrape configuration containing exactly one endpoint to scrape:
+# Here it's Prometheus itself.
+scrape_configs:
+ # The job name is added as a label `job=` to any timeseries scraped from this config.
+ - job_name: 'prometheus'
+
+ # metrics_path defaults to '/metrics'
+ # scheme defaults to 'http'.
+
+ static_configs:
+ - targets: ['localhost:9090']
+
+ # The job name is added as a label `job=` to any timeseries scraped from this config.
+ - job_name: 'jboot'
+ static_configs:
+ - targets: ['localhost:1234']
-```
-docker run -d -p 3000:3000 grafana/grafana
```
-同时,需要在 jboot 应用添加如下依赖:
+- 3、通过 ./prometheus --config.file=prometheus.yml 启动 Prometheus
+- 4、启动成功后,我们可以通过 `http://127.0.0.1:9090` 访问到 Prometheus 的查询页面,在 `http://127.0.0.1:9090/targets` 可以看到如下图所示:
+
+
+
+其中,jboot 应用是红色的,state 处于 down 的状态,原因是 Jboot 应用还未启动。
+
+
+
+
+最后,需要在 jboot 应用添加如下依赖:
```xml
- com.github.davidb
- metrics-influxdb
- 1.1.0
+ io.prometheus
+ simpleclient_dropwizard
+ 0.11.0
+ provided
+
+
+
+ io.prometheus
+ simpleclient_httpserver
+ 0.11.0
+ provided
```
和 在 jboot.properties 添加如下配置:
-```
-jboot.metric.url=/metrics_admin
-jboot.metric.reporter=influxdb
-jboot.metric.reporter.influxdb.host=127.0.0.1
-jboot.metric.reporter.influxdb.port=8086
-jboot.metric.reporter.influxdb.user=admin
-jboot.metric.reporter.influxdb.password=123456
-jboot.metric.reporter.influxdb.dbName=metricsDb
+```properties
+jboot.metric.enable=true
+jboot.metric.reporter=prometheus
```
当然,要监控某个方法的相关输入,还需要通过注解来进行配置
@@ -105,25 +151,53 @@ public class MetricsController extends JbootController {
}
```
-启动 jboot,当访问 `http://127.0.0.1:8080/` 之后, jboot 就会把Metrics的数据输出到 influxdb,此时我们就可以配置 grafana 读取 influxdb 的数据了。
+启动 jboot,当访问 `http://127.0.0.1:8080/` 之后, jboot 就会把 Metrics 的数据输出到 prometheus ,此时我们就可以配置 grafana 读取 prometheus 的数据了。
-更多关于 grafana 读取 influxdb 的文档请参考 https://grafana.com/docs/features/datasources/influxdb/ 。
+**配置 Grafana 读取 Prometheus 的数据**
+- 启动 Grafana
-## Metrics 输出到 graphite
+```shell
+docker run -d -p 3000:3000 grafana/grafana
+```
-在开始之前,需要添加如下的 Maven 依赖。
+- 进入 Grafana 的后台
+
+ 通过网址 `http://127.0.0.1:3000` 可以访问到 Grafana,首次访问需要登录,默认账号和密码都是 admin。
-```xml
-
- io.dropwizard.metrics
- metrics-graphite
- 4.1.0
-
-```
+- 导入 Jboot JVM 的 Grafana 大盘配置
-**启动 graphite**
+
-```
+
+
+- 为 Grafana 添加 Prometheus 的数据源
+
+
+
+
+在 `Import via panel json` 中输入 `https://gitee.com/JbootProjects/jboot/raw/master/doc/jboot_jvm_grafana.json` 中的内容,然后点击 load,就可以见到如下的 JVM 大图了。
+
+
+
+
+## Metrics 输出到 Graphite
+
+Graphite 是一个开源实时的、显示时间序列度量数据的图形系统。Graphite 并不收集度量数据本身,而是像一个数据库,通过其后端接收度量数据,然后以实时方式查询、转换、组合这些度量数据。Graphite支持内建的Web界面,它允许用户浏览度量数据和图。
+
+Graphite 有三个主要组件组成:
+
+- 1)Graphite-Web
+这是一个基于Django的Web应用,可以呈现图形和仪表板
+
+- 2)Carbon
+这是一个度量处理守护进程
+
+- 3)Whisper
+这是一个基于时序数据库的库
+
+在开始之前,我们需要启动 Graphite
+
+```shell
docker run -d\
--name graphite\
--restart=always\
@@ -135,10 +209,21 @@ docker run -d\
graphiteapp/graphite-statsd
```
-在 jboot.properties 添加如下配置:
+然后,在我们自己的项目添加如下的 Maven 依赖。
+
+```xml
+
+ io.dropwizard.metrics
+ metrics-graphite
+ 4.1.0
+
```
-jboot.metric.url=/metrics_admin
+
+最后在 jboot.properties 添加如下配置:
+
+```properties
+jboot.metric.enable=true
jboot.metric.reporter=graphite
jboot.metric.reporter.graphite.host=127.0.0.1
jboot.metric.reporter.graphite.port=2003
diff --git a/doc/docs/mq.md b/doc/docs/mq.md
index 339a75f28fe7cab8d9015c550ede5197a9908782..b2c8b376c7fa34cab1e4790e65914fcc54339e4d 100644
--- a/doc/docs/mq.md
+++ b/doc/docs/mq.md
@@ -1,10 +1,12 @@
# MQ 消息队列
+## 基本使用
+
Jboot 内置了对MQ消息队列的功能支持,使用MQ需要以下几步步骤。
**第一步:配置jboot.properties文件,内容如下:**
-```
+```properties
# 默认为redis (支持: redis,activemq,rabbitmq,hornetq,aliyunmq等 )
jboot.mq.type = redis
jboot.mq.channel = channel1,channel2,channel3
@@ -32,11 +34,117 @@ Jboot.getMq().startListening();
配置完毕后,我们在其他服务器,就可以通过如下代码发送消息:
-```
+```java
Jboot.getMq().publish(yourObject, "channel1");
```
需要注意的是,两个服务器的 mq 类型、和服务器信息一定是一致的。消息的接收端的 `jboot.mq.channel` 配置必须包含 "channel1" 才能正常接收数据 。
-其他更多关于 rabbitmq、aliyunmq、qpidmq 等的配置,需要查看 [这里](./config.md)。
\ No newline at end of file
+其他更多关于 rabbitmq、aliyunmq、qpidmq 等的配置,需要查看 [这里](./config.md)。
+
+## Rabbitmq
+
+```properties
+#设置 mq 的相关信息
+jboot.mq.type=rabbitmq
+jboot.mq.channel=channel1
+
+
+#以下可以不用配置,是默认信息
+jboot.mq.rabbitmq.username=guest
+jboot.mq.rabbitmq.password=guest
+jboot.mq.rabbitmq.host=127.0.0.1
+jboot.mq.rabbitmq.port=5672
+jboot.mq.rabbitmq.virtualHost=
+
+#非常重要,多个应用如果同时接受同一个 channel 的广播,必须配置此项,而且必须不能相同,否则广播的时候只有一个应用能够接受到
+jboot.mq.rabbitmq.broadcastChannelPrefix=app1
+```
+
+## Redis-mq
+
+```properties
+jboot.mq.type=redis
+jboot.mq.channel=channel1,channel2,myChannel
+jboot.mq.redis.host=127.0.0.1
+jboot.mq.redis.port=6379
+jboot.mq.redis.timeout=2000
+jboot.mq.redis.password
+jboot.mq.redis.database
+jboot.mq.redis.clientName
+jboot.mq.redis.testOnCreate
+jboot.mq.redis.testOnBorrow
+jboot.mq.redis.testOnReturn
+jboot.mq.redis.testWhileIdle
+jboot.mq.redis.minEvictableIdleTimeMillis
+jboot.mq.redis.timeBetweenEvictionRunsMillis
+jboot.mq.redis.numTestsPerEvictionRun
+jboot.mq.redis.maxAttempts
+jboot.mq.redis.maxTotal
+jboot.mq.redis.maxIdle
+jboot.mq.redis.minIdle
+jboot.mq.redis.maxWaitMillis
+```
+
+## Rocketmq
+
+```properties
+jboot.mq.type=rocketmq
+jboot.mq.channel=channel1,channel2,myChannel
+jboot.mq.rocket.namesrvAddr=127.0.0.1:9876
+jboot.mq.rocket.namespace
+jboot.mq.rocket.consumerGroup = "jboot_default_consumer_group";
+jboot.mq.rocket.consumeMessageBatchMaxSize
+jboot.mq.rocket.broadcastChannelPrefix "broadcast-";
+jboot.mq.rocket.producerGroup "jboot_default_producer_group";
+```
+
+## 多 MQ 实例
+
+若在一个应用在,里面有多个 MQ 实例,假设有两个 redis 实例和两个 Rabbitmq 实例,配置如下:
+
+```properties
+# 默认 MQ 及其类型
+jboot.mq.type=redis
+jboot.mq.channel=channel1,channel2,myChannel
+
+jboot.mq.redis.host=127.0.0.1
+jboot.mq.redis.port=6379
+jboot.mq.redis.timeout=2000
+
+
+# 其他另一个 mq1 的类型
+jboot.mq.other1.type=redis
+jboot.mq.other1.typeName=redis1
+jboot.mq.other1.channel=channel1,channel2....
+
+
+jboot.mq.redis.redis1.host=127.0.0.1
+jboot.mq.redis.redis1.port=6379
+jboot.mq.redis.redis1.timeout=2000
+
+
+# 其他另一个 mq2 的类型
+jboot.mq.other2.type=rabbitmq
+jboot.mq.other2.typeName=rabbitmqaaa
+jboot.mq.other2.channel=channel1,channel2....
+
+jboot.mq.rabbitmq.rabbitmqaaa.username=guest
+jboot.mq.rabbitmq.rabbitmqaaa.password=guest
+jboot.mq.rabbitmq.rabbitmqaaa.host=127.0.0.1
+jboot.mq.rabbitmq.rabbitmqaaa.port=5672
+
+# 其他另一个 mq3 的类型
+jboot.mq.other3.type=rabbitmq
+jboot.mq.other3.typeName=rabbitmqbbb
+jboot.mq.other3.channel=channel1,channel2....
+
+jboot.mq.rabbitmq.rabbitmqbbb.username=guest
+jboot.mq.rabbitmq.rabbitmqbbb.password=guest
+jboot.mq.rabbitmq.rabbitmqbbb.host=127.0.0.1
+jboot.mq.rabbitmq.rabbitmqbbb.port=5672
+
+
+```
+
diff --git a/doc/docs/mvc.md b/doc/docs/mvc.md
index 43fcf2ad1d378680423ba4ef43a03e2064f66889..904c728364c91e8443bf52dc9771197bb7f7a7d6 100644
--- a/doc/docs/mvc.md
+++ b/doc/docs/mvc.md
@@ -7,14 +7,11 @@
- Controller : 控制器
- Action :请求的基本单位
- Interceptor : 拦截器
-- FixedInterceptor :永久拦截器
- Handler : 处理器
- Render :渲染器
- Session
- Cookie
-- Jwt : Json Web Token
-- Validate : 验证器
-- 安全
+
## 描述
@@ -27,9 +24,141 @@ JFinal 的相关文档: [https://www.jfinal.com/doc/3-1](https://www.jfinal.co
Controller 是 JFinal 的核心类之一,是 MVC 设计模式中的控制器。基于 Jboot 开发的控制器需要继承 Controller。Controller 也是定义 Action 方法的地点,一个 Controller 可以包含多个 Action 。
- 另外,JbootController 扩展了 JFinal 的 Controller 类,增加了 Jwt、FlashMessage 和 其他一些实用的方法。
+## Jboot 增强的 Controller 功能
+
+#### 增强功能1:缓存功能
+
+例如:
+```java
+@Cacheable(name = "aaa", liveSeconds = 10)
+public void json() {
+
+ System.out.println("json() invoked!!!!!!!!!");
+
+ Map data = new HashMap<>();
+ data.put("age", 1);
+ data.put("name", "张三");
+ data.put("sex", 1);
+
+ renderJson(data);
+}
+```
+以上的代码中,在 10 秒钟内多次访问的时候,只有第一次访问到了 json() 方法,其余的都直接使用第一次的结果输出给浏览器。其中,缓存的名称为
+"aaa",key 为 `class.method()` 格式。我们可以通过 `@Cacheable(name = "aaa", key="xxx")` 来指定缓存的key值。
+
+其中 key 里的内容还可以使用 `#(方法参数名称)` 和 `@para('http参数名称')` 的方式来定义 key。
+
+例如:
+```java
+@Cacheable(name = "aaa", key="#para('mykey')", liveSeconds = 10)
+public void json() {
+
+ System.out.println("json() invoked!!!!!!!!!");
+
+ Map data = new HashMap<>();
+ data.put("age", 1);
+ data.put("name", "张三");
+ data.put("sex", 1);
+
+ renderJson(data);
+}
+```
+当访问 `http://127.0.0.1:8080/json?mykey=thekey` 的时候,该缓存的 key 的内容为:"thekey"。
+
+但是在某些极端场景下,我们可能是不需要缓存的,配置如下:
+
+```java
+ @Cacheable(name = "aaa", liveSeconds = 10, unless = "para('type')==1")
+ public void json2() {
+ System.out.println("json2() invoked!!!!!!!!!");
+ Map data = new HashMap<>();
+ data.put("age", 1);
+ data.put("name", "张三");
+ data.put("sex", 1);
+ renderJson(data);
+ }
+```
+
+当访问 `http://127.0.0.1:8080/json2?type=1` 的时候,永远不会命中缓存。
+
+#### 增强功能2:返回值自动渲染功能
+
+例如:
+
+```java
+@RequestMapping("/")
+public MyController extends Controller{
+
+ //自动使用 html 来渲染
+ public String test1(){
+ return "test1.html";
+ }
+
+ //自动使用文本来渲染
+ public String test2(){
+ return "test2...";
+ }
+
+ //渲染 404 错误
+ public String test3(){
+ return "error: 404";
+ }
+
+ //渲染 500 错误
+ public String test4(){
+ return "error: 500";
+ }
+
+ //自动 redirect 跳转
+ public String test5(){
+ return "redirect: /to/your/path";
+ }
+
+ //自动 forward action 重定向
+ public String test6(){
+ return "forward: /to/your/path";
+ }
+
+ //自动进行文件下载
+ public File test7(){
+ return new File('/file/path');
+ }
+
+ //渲染 json 内容
+ public Object test8(){
+ Map map = new HashMap<>();
+ map.put("key","value....");
+ return map;
+ }
+
+ //使用自动的 render 渲染
+ public Object test9(){
+ Render render = new TextRender("some text...");
+ return render;
+ }
+
+}
+```
+
+#### 增强功能3:XSS 功能防护功能
+
+在 jboot 中,只需要我们在 jboot.properties 添加如下配置,并能开启全局 xss 防护功能:
+
+```properties
+jboot.web.escapeParasEnable = true
+```
+所以的 http 请求都会自动进行 escape 编码,防止 xss 攻击。若想在某些极端场景下获取原始内容,只需要我们在 Controller 里通过
+getOriginalPara('key'); 既可以获得原始的内容,此时,需要自己进行 XSS 内容过滤或者对内容进行安全编码。
+
+#### 增强功能4:更加强大的验证器功能
- 建议基于 Jboot 开发的应用,都继承至 JbootController。(备注:只是建议、而非必须。)
+详情:http://www.jboot.com.cn/docs/validator.html
+
+#### 更多的增强功能
+
+Jboot 还对 web 模块做了许多其他的增强,比如 1、分布式 session 的支持; 2、json 增强,前端传入 json 内容可以直接注入到 model 或者 bean;
+3、更多的模板指令,比如前端的分页指令等。4、@EnableCORS 对跨域的支持。 5、更加方便的枚举类在 模板 里的使用。6、提供 @GetRequest、@PostRequest
+对 Controller 方法的限制 等等等等。
## Action
@@ -40,9 +169,6 @@ Action 的相关文档请参考: [https://www.jfinal.com/doc/3-2](https://www.
Interceptor 拦截器的相关文档请参考 [https://www.jfinal.com/doc/4-2](https://www.jfinal.com/doc/4-2)
-## FixedInterceptor :永久拦截器
-
-FixedInterceptor 的用法和 Interceptor 一样,但是 `FixedInterceptor` 不会被 `@Clear` 清除,常用于给 Controller 或者 Service 的注解进行增强。
## Handler : 处理器
@@ -54,80 +180,69 @@ Render 请参加 JFinal 的文档 [https://www.jfinal.com/doc/3-7](https://www.j
## Session
-Jboot 增强了 JFinal 的 Session 功能,同时 Session 默认使用了 Jboot 自带的缓存实现,当 Jboot 的缓存使用分布式缓存之后(比如 redis )。Session 就会自动有了分布式 Session 的功能。
+Jboot 增强了 JFinal 的 Session 功能,同时 Session 默认使用了 Jboot 自带的缓存实现,当 Jboot 开启分布式缓存之后(比如 redis )。Session 就会自动有了分布式 Session 的功能。
-当然,也可以通过如下来配置 Session 特殊功能:
+开启分布式缓存,值需要添加如下配置:
-```
-jboot.web.session.cookieName #cookie 的名称
-jboot.web.session.cookieDomain #cookie 的域名
-jboot.web.session.cookieContextPath #cookie 的路径
-jboot.web.session.maxInactiveInterval #cookie 的刷新时间
-jboot.web.session.cookieMaxAge #cookie 的有效时间
-jboot.web.session.cacheName #Session的缓存名称
-jboot.web.session.cacheType #Session的缓存类型(不配置的情况使用jboot的默认缓存)
-```
+```properties
+jboot.cache.type = redis
+jboot.cache.redis.host = 127.0.0.1
+jboot.cache.redis.port = 3306
+jboot.cache.redis.password
+jboot.cache.redis.database
+jboot.cache.redis.timeout
+```
-# Jwt
+> 更多关于缓存的配置请参考【缓存】章节
- Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
-**JWT的方法:**
+添加以上配置后,我们在 Controller 中就可以使用如下代码操作 Session 了。
-|方法调用 | 描述 |
-| ------------- | -----|
-| setJwtAttr()| 设置 jwt 的 key 和 value |
-| setJwtMap()| 把整个 map的key和value 设置到 jwt |
-| getJwtAttr()| 获取 已经设置进去的 jwt 信息 |
-| getJwtAttrs()| 获取 所有已经设置进去的 jwt 信息|
-| getJwtPara()| 获取客户端传进来的 jwt 信息,若 jwt 超时或者不被信任,那么获取到的内容为null |
+```java
+@RequestMapping("/")
+public class MyController extends JbootController {
+ public void index() {
+
+ //设置 session 内容
+ setSessionAttr("attr", "your session value");
-**JWT的相关配置**
+ renderText("hello world");
+ }
+}
+```
-|配置属性 | 描述 |
-| ------------- | -----|
-| jboot.web.jwt.httpHeaderName| 配置JWT的http头的key,默认为JWT |
-| jboot.web.jwt.secret | 配置JWT的密钥 |
-| jboot.web.jwt.validityPeriod | 配置JWT的过期时间,默认不过期 |
+当然,也可以通过如下来对 Session 进行更多的配置:
+```
+jboot.web.session.cookieName #cookie 的名称
+jboot.web.session.cookieDomain #cookie 的域名
+jboot.web.session.cookieContextPath #cookie 的路径
+jboot.web.session.maxInactiveInterval #cookie 的刷新时间
+jboot.web.session.cookieMaxAge #cookie 的有效时间
+jboot.web.session.cacheName #Session的缓存名称
+jboot.web.session.cacheType #Session的缓存类型(不配置的情况使用jboot的默认缓存)
+```
-## Validate : 验证器
+## Cookie
-Jboot 提供了一些列的 validate 注解,方便用户对 Controller 进行数据验证。
+Jboot 增强了 JFinal 的 Cookie 功能,同时提供了 CookieUtil 工具类,用于对 Cookie 进行加密安全保护 Cookie 信息安全。
-- CaptchaValidate 对验证码进行验证
-- EmptyValidate 对空内容进行验证
-- UrlParaValidate 对URl参数内容进行验证
+```java
-使用方法:
+//设置 Cookie 数据
+CookieUtil.put(controller,"key","value");
-```java
-@RequestMapping("/validate")
-public class ValidateController extends Controller {
+//读取 Cookie 数据
+CookieUtil.get(controller,"key")
+```
- public void index(){
- renderText("index");
- }
+```properties
+jboot.web.cookieEncryptKey = cookie安全秘钥
+jboot.web.cookieMaxAge = 60 * 60 * 24 * 2
+```
- // 访问 /validate/test1 不通过,必须是 /validate/test1/data 才会通过
- @UrlParaValidate
- public void test1(){
- renderText("test1");
- }
- // 访问 /validate/test2 不通过,浏览器会显示内容 :test2 was verification failed
- @UrlParaValidate(renderType = ValidateRenderType.TEXT,message = "test2 was verification failed")
- public void test2(){
- renderText("test2");
- }
- // 访问 /validate/test3 不通过,必须传入 form 数据
- @EmptyValidate(value = @Form(name = "form"),renderType = ValidateRenderType.JSON)
- public void test3(){
- renderText("test3");
- }
-}
-```
\ No newline at end of file
diff --git a/doc/docs/quickstart.md b/doc/docs/quickstart.md
index 935e4d74c1b5df8a1cd1c6f195863f4a8f029971..a72bf0ab4fe373e41079806c35b2ca43fc679875 100644
--- a/doc/docs/quickstart.md
+++ b/doc/docs/quickstart.md
@@ -28,7 +28,7 @@
io.jboot
jboot
- 3.0.1
+ 4.1.0
```
diff --git a/doc/docs/readme.md b/doc/docs/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..90522646b26f9a7fbae25e90920976776874c032
--- /dev/null
+++ b/doc/docs/readme.md
@@ -0,0 +1,67 @@
+# Jboot 简介
+
+[[toc]]
+
+## 简介
+
+Jboot 是一个基于 JFinal、JFinal-Undertow、Dubbo、Seata、Sentinel、ShardingSphere、Nacos 等开发的微服务框架,
+帮助开发者降低微服务开发门槛。同时完美支持在 idea、eclipse 下多 maven 模块,对 java 代码、html、css、js 等资源文件进行热加载。爽爽开发,快乐生活。
+
+
+
+## 特点
+
+目前已经开源超过了 3 年的时间,迭代了 100+ 个版本,已经被超过 1000+ 公司在使用。
+
+Jboot 主要有以下特征:
+
+- 1、基于 JFinal 的 MVC + ORM 快速开发。
+- 2、基于 ShardingSphere + Seata 分布式事务 和 分库分表。
+- 3、基于 Dubbo 或 Motan 的 RPC 实现
+- 4、基于 Sentinel 的分布式限流和降级
+- 5、基于 Apollo 和 Nacos 的分布式配置中心
+- 6、基于 EhCache 和 Redis 的分布式二级缓存
+
+## 微信群
+
+
+
+## QQ群
+
+群1: 601440615(已满)
+群2: 719614554
+
+## 文档目录
+
+- [安装](install.md)
+- [2分钟快速开始](quickstart.md)
+- [热加载](hotload.md)
+- [Undertow](undertow.md)
+- [配置](config.md)
+- [JFinalConfig](jfinalConfig.md)
+- [WebSocket](websocket.md)
+- [MVC](mvc.md)
+- [AOP](aop.md)
+- [数据库操作](db.md)
+- [缓存](cache.md)
+- [RPC远程调用](rpc.md)
+- [MQ消息队列](mq.md)
+- [Gateway 网关](gateway.md)
+- [任务调度](schedule.md)
+- [限流](limit.md)
+- [监控](metrics.md)
+- [序列化](serialize.md)
+- [事件机制](event.md)
+- [SPI扩展机制](spi.md)
+- [代码生成器](codegen.md)
+- [项目构建](build.md)
+- [项目部署](deploy.md)
+- [Jboot与Docker](docker.md)
+- [1.x 升级到 2.x 教程](upgrade.md)
+- [交流社区、QQ群和微信群](communication.md)
+- 第三方组件的支持
+ - [sentinel 限流降级](sentinel.md)
+ - [redis](redis.md)
+ - [shiro](shiro.md)
+ - [jwt](jwt.md)
+ - [swagger](swagger.md)
diff --git a/doc/docs/redis.md b/doc/docs/redis.md
index e4c1d67de4077c13b22237846454ada5440c3f30..68a1c7ecf11347ea8e87c1c5cdcd93a24f40e6e2 100644
--- a/doc/docs/redis.md
+++ b/doc/docs/redis.md
@@ -29,14 +29,14 @@ Redis 优势:
## Redis的配置
在使用 Redis 之前,先进行 Redis 配置,配置内容如下:
-```
+```properties
jboot.redis.host=127.0.0.1
jboot.redis.password=xxxx
```
Redis 还支持如下的更多功能的配置:
-```
+```properties
jboot.redis.port = 6379
jboot.redis.timeout = 2000
jboot.redis.database
@@ -62,7 +62,7 @@ jboot.redis.serializer
配置后,就可以通过如下代码获取 JbootRedis 对redis进行操作:
-```
+```java
JbootRedis redis = Jboot.getRedis();
redis.set("key1","value1");
@@ -176,17 +176,17 @@ JbootRedis 是通过 `jedis` 或者 `JedisCluster` 进行操作的,如果想
JbootRedis redis = Jboot.me().getReids();
//单机模式下
-JbootRedisImpl redisImpl = (JbootRedisImpl)redis;
+JbootJedisImpl redisImpl = (JbootJedisImpl)redis;
Jedis jedis = redisImpl.getJedis();
//集群模式下
-JbootClusterRedisImpl redisImpl = (JbootClusterRedisImpl)redis;
+JbootJedisClusterImpl redisImpl = (JbootJedisClusterImpl)redis;
JedisCluster jedis = redisImpl.getJedisCluster();
```
## Redis集群
在单机模式下,配置文件如下:
-```
+```properties
jboot.redis.host=127.0.0.1
jboot.redis.password=xxxx
```
@@ -194,7 +194,7 @@ jboot.redis.password=xxxx
在集群模式下,只需要在 jboot.redis.host 配置为多个主机即可,例如:
-```
+```properties
## 多个IP用英文逗号隔开,端口号用英文冒号(:)。
Jboot.redis.host=192.168.1.33,192.168.1.34:3307,192.168.1.35
jboot.redis.password=xxxx
diff --git a/doc/docs/rpc.md b/doc/docs/rpc.md
index 30116bc32ed3e49055405ec2851d1c6f50406705..349e61365a08d91518152dde3f826139f154c4b4 100644
--- a/doc/docs/rpc.md
+++ b/doc/docs/rpc.md
@@ -1,29 +1,136 @@
# RPC 远程调用
+## 说明
+
+此文档只适用于 jboot v3.1.0 以上,之前的版本请参考 [这里](./rpc2.x.md) 。
+
## 目录
+- 添加依赖
- 配置
- 开始使用
+- restful 暴露
- 高级功能
+## 添加依赖
+
+Jboot 支持 dubbo 和 motan,假设我们需要使用 dubbo 作为底层的 RPC 框架,需要添加如下依赖:
+
+```xml
+
+ org.apache.dubbo
+ dubbo
+ ${dubbo.version}
+
+
+ org.springframework
+ spring-context
+
+
+ com.alibaba.spring
+ spring-context-support
+
+
+
+
+
+ org.apache.dubbo
+ dubbo-dependencies-zookeeper
+ ${dubbo.version}
+ pom
+
+```
+
+如果使用 motan,需要添加如下依赖:
+
+```xml
+
+ com.weibo
+ motan-core
+ ${motan.version}
+
+
+
+ com.weibo
+ motan-transport-netty4
+ ${motan.version}
+
+
+
+
+ com.weibo
+ motan-registry-consul
+ ${motan.version}
+
+
+
+ com.weibo
+ motan-registry-zookeeper
+ ${motan.version}
+
+```
+
## 配置
-在 Jboot 中,默认实现了对 Dubbo、motan 的 RPC 调用支持。在使用 RPC 远程调用之前,需要做一些基本的配置。
+在 Jboot 中默认实现了对 Dubbo、motan 的 RPC 调用支持。在使用 RPC 远程调用之前,需要做一些基本的配置。
例如 :
-```
+
+```properties
jboot.rpc.type = dubbo
-jboot.rpc.callMode = direct
-jboot.rpc.directUrl = 127.0.0.1:8000
+jboot.rpc.urls = com.yourdomain.AAAService:127.0.0.1:8080,com.yourdomain.XXXService:127.0.0.1:8080
+jboot.rpc.providers = com.yourdomain.AAAService:providerName,com.yourdomain.XXXService:providerName
+jboot.rpc.consumers = com.yourdomain.AAAService:consumerName,com.yourdomain.XXXService:consumerName
+jboot.rpc.defaultVersion = 1.0.0
+jboot.rpc.versions = com.yourdomain.AAAService:1.0.0,com.yourdomain.XXXService:1.0.1
+jboot.rpc.defaultGroup =
+jboot.rpc.groups = com.yourdomain.AAAService:group1,com.yourdomain.XXXService:group2
+jboot.rpc.autoExportEnable = true
+```
+
+- jboot.rpc.type : RPC 的类型,目前只支持 dubbo 和 motan
+- jboot.rpc.urls : 一般不用配置,只有直连模式下才会去配置,此处是配置 Service接口和URL地址的映射关系。
+- jboot.rpc.providers : 配置 Service 和 Provider 的映射关系( Motan下配置的是 Service 和 BasicService 的映射关系)。
+- jboot.rpc.consumers : 配置 Reference 和 consumer 的映射关系( Motan下配置的是 Referer 和 BaseReferer 的映射关系)。
+- jboot.rpc.defaultVersion : 当service不配置版本时,默认的版本号,默认值为 1.0.0
+- jboot.rpc.versions : 每个服务对应的版本号
+- jboot.rpc.defaultGroup : 当服务不配置 group 时,默认的 gourp
+- jboot.rpc.groups : 每个服务对应的 group
+- jboot.rpc.autoExportEnable : 当 Jboot 启动的时候,是否自动暴露 @RPCBean 注解的接口。
+
+在 以上 示例中,`jboot.rpc.providers` 配置中,可以对每个 Service 进行配置,但是,在绝大多数的情况下,我们可能只需要一个配置,这个配置应用于所有的 Service 服务,此时我们需要做如下配置:
+
+```properties
+# 名称为 default 的 provider 配置(当不配置其名称的时候,名称默认为 default)
+jboot.rpc.dubbo.provider.timeout = xx.xx.xx
+jboot.rpc.dubbo.provider.loadbalance = xx.xx.xx
+jboot.rpc.dubbo.provider.group = xx.xx.xx
+jboot.rpc.dubbo.provider.host = xx.xx.xx
+jboot.rpc.dubbo.provider.default = true #设置当前 provider 为默认配置,既所有未指定 provider 的 service 服务都使用此配置。
+
+# 名称为 name1 的 provider 配置
+jboot.rpc.dubbo.provider.name1.timeout = xx.xx.xx
+jboot.rpc.dubbo.provider.name1.loadbalance = xx.xx.xx
+jboot.rpc.dubbo.provider.name1.group = xx.xx.xx
+jboot.rpc.dubbo.provider.name1.host = xx.xx.xx
+
+# 名称为 name2 的 provider 配置
+jboot.rpc.dubbo.provider.name2.timeout = xx.xx.xx
+jboot.rpc.dubbo.provider.name2.loadbalance = xx.xx.xx
+jboot.rpc.dubbo.provider.name2.group = xx.xx.xx
+jboot.rpc.dubbo.provider.name2.host = xx.xx.xx
+
+
+# 配置 com.yourdomain.AAAService 使用的 provider 配置为 name1
+# 配置 com.yourdomain.AAAService 使用的 provider 配置为 name2
+# 其他所有服务的 provider 配置使为 default,原因是名称为 default 的 provider 其属性 default 为 true 了
+# 此处要注意,如果我们给 name2 的 provider 添加配置 jboot.rpc.dubbo.provider.name2.default = true,
+# 那么所有的未配置 providers 的服务都使用 name2 作为其默认位置。
+jboot.rpc.providers = com.yourdomain.AAAService:name1,com.yourdomain.XXXService:name2
```
-- jboot.rpc.type : RPC 的类型,不配置默认为 Dubbo
-- jboot.rpc.callMode : RPC 的调用方式
- - direct : 直联模式
- - registry : 注册中心模式(服务注册和服务发现)
-- jboot.rpc.directUrl : 当 callMode 为 direct 直联模式时,需要配置直联模式的服务器 IP 地址和端口号。
+provider 的更多配置情况参考:https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-provider/
-> 更多的配置请查看 [config.md](./config.md)
## 开始使用
@@ -31,14 +138,13 @@ jboot.rpc.directUrl = 127.0.0.1:8000
- 1、定义接口
- 2、编写实现类
-- 3、启动 Server 暴露服务
+- 3、启动 Server(Provider) 暴露服务
- 4、启动客户端、通过 RPC 调用 Server 提供的服务
**定义接口**
```java
-
public interface BlogService {
public String findById();
@@ -48,9 +154,8 @@ public interface BlogService {
**编写实现类**
-```java
-
+```java
@RPCBean
public class BlogServiceProvider implements BlogService {
@@ -66,13 +171,33 @@ public class BlogServiceProvider implements BlogService {
}
```
+### Dubbo
**启动 Server 暴露服务**
-```java
+```java
public class DubboServer {
public static void main(String[] args) {
+
+ JbootApplication.setBootArg("undertow.port", "9998");
+
+ JbootApplication.setBootArg("jboot.rpc.type", "dubbo");
+
+ // 开启 @RPCBean 自动暴露功能,默认情况下是开启的,无需配置,
+ // 但是此测试代码的 jboot.properties 文件关闭了,这里需要开启下
+ JbootApplication.setBootArg("jboot.rpc.autoExportEnable", true);
+
+ //dubbo 的通信协议配置
+ JbootApplication.setBootArg("jboot.rpc.dubbo.protocol.name", "dubbo");
+ JbootApplication.setBootArg("jboot.rpc.dubbo.protocol.port", "28080");
+
+
+ // dubbo 注册中心的配置,
+ // 当不配置注册中心的时候,默认此服务只提供了直联模式的请求
+ // JbootApplication.setBootArg("jboot.rpc.dubbo.registry.protocol", "zookeeper");
+ // JbootApplication.setBootArg("jboot.rpc.dubbo.registry.address", "127.0.0.1:2181");
+
JbootApplication.run(args);
System.out.println("DubboServer started...");
@@ -81,17 +206,27 @@ public class DubboServer {
}
```
+>备注:以上的 JbootApplication.setBootArg() 里设置的内容,都可以配置到 jboot.properties 里。例如: `JbootApplication.setBootArg("jboot.rpc.autoExportEnable", true);` 在 jboot.properties
+>里对应的配置是 `jboot.rpc.autoExportEnable = true` 。
-**启动客户端、通过 RPC 调用 Server 提供的服务**
-```java
+
+**启动 dubbo 客户端、通过 RPC 调用 Server 提供的服务**
+
+```java
@RequestMapping("/dubbo")
public class DubboClient extends JbootController{
public static void main(String[] args) {
- //Undertow端口号配置
- JbootApplication.setBootArg("undertow.port", "8888");
+ //Undertow端口号配置
+ JbootApplication.setBootArg("undertow.port", "9999");
+
+ //RPC配置
+ JbootApplication.setBootArg("jboot.rpc.type", "dubbo");
+
+ //设置直连模式,方便调试,默认为注册中心
+ JbootApplication.setBootArg("jboot.rpc.urls", "io.jboot.test.rpc.commons.BlogService:127.0.0.1:28080");
JbootApplication.run(args);
}
@@ -104,53 +239,426 @@ public class DubboClient extends JbootController{
System.out.println(blogService);
renderText("blogId : " + blogService.findById());
}
-
}
```
+### Motan
-## 高级功能
+**Motan 服务端**
-在以上的示例中,使用到了两个注解:
-- @RPCBean,标示当前服务于RPC服务
-- @RPCInject,RPC注入赋值
+```java
+public class MotanServer {
-虽然以上示例中,`@RPCBean` 和 `@RPCInject` 没有添加任何的参数,但实际是他们提供了非常丰富的配置。
+ public static void main(String[] args) throws InterruptedException {
-以下分别是 `@RPCBean` 和 `@RPCInject` 的定义:
-RPCBean :
-```java
+ JbootApplication.setBootArg("jboot.rpc.type", "motan");
+
+ // 开启 @RPCBean 自动暴露功能,默认情况下是开启的,无需配置,
+ // 但是此测试代码的 jboot.properties 文件关闭了,这里需要开启下
+ JbootApplication.setBootArg("jboot.rpc.autoExportEnable", true);
+
+ // motan 与 dubbo 不一样,motan 需要配置 export,
+ // export 配置内容为 协议ID:端口号,默认的协议 id 为 default
+ JbootApplication.setBootArg("jboot.rpc.motan.defaultExport", "default:28080");
-public @interface RPCBean {
+ // motan 的注册中心的协议
+ // JbootApplication.setBootArg("jboot.rpc.motan.registry.regProtocol", "zookeeper");
+ //注册中心地址,即zookeeper的地址
+ // JbootApplication.setBootArg("jboot.rpc.motan.registry.address", "127.0.0.1:2181");
- int port() default 0;
- int timeout() default -1;
- int actives() default -1;
- String group() default "";
- String version() default "";
- //当一个Service类实现对个接口的时候,
- //可以通过这个排除不暴露某个实现接口
- Class[] exclude() default Void.class;
+
+
+ JbootSimpleApplication.run(args);
+
+ System.out.println("MotanServer started...");
+
+ }
}
+
```
-RPCInject :
+**Motan 客户端**
```java
+@RequestMapping("/motan")
+public class MotanClient extends JbootController {
+
+ public static void main(String[] args) {
+
+
+ //Undertow端口号配置
+ JbootApplication.setBootArg("undertow.port", "9999");
+
+ //RPC配置
+ JbootApplication.setBootArg("jboot.rpc.type", "motan");
+ JbootApplication.setBootArg("jboot.rpc.autoExportEnable", false);
+
+ //设置直连模式,方便调试,默认为注册中心
+ JbootApplication.setBootArg("jboot.rpc.urls", "io.jboot.test.rpc.commons.BlogService:127.0.0.1:28080");
+
+
+ // motan 的注册中心的协议
+ // JbootApplication.setBootArg("jboot.rpc.motan.registry.regProtocol", "zookeeper");
+ //注册中心地址,即zookeeper的地址
+ // JbootApplication.setBootArg("jboot.rpc.motan.registry.address", "127.0.0.1:2181");
+
+ JbootApplication.run(args);
+ }
+
+
+ @RPCInject
+ private BlogService blogService;
+
+// @Before(MotanInterceptor.class)
+ public void index() {
+
+ System.out.println("blogService:" + blogService);
+
+ renderText("blogId : " + blogService.findById());
+ }
+
+
+}
+```
+
+
+## restful 暴露
+
+在某些情况下,我们希望 rpc service 通过 restful 协议暴露给其他客户端(或者其他编程语言)去使用,我们需要添加如下的依赖。
+
+PS:目前只有 dubbo 支持了 restful 协议,其他 rpc 框架暂时不支持。
+
+
+```xml
+
+
+ io.netty
+ netty-all
+ ${io.netty.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ ${org.jboss.resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-client
+ ${org.jboss.resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-netty4
+ ${org.jboss.resteasy.version}
+
+
+
+ javax.validation
+ validation-api
+ 1.1.0.Final
+
+
+
+ org.jboss.resteasy
+ resteasy-jackson-provider
+ ${org.jboss.resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-jaxb-provider
+ ${org.jboss.resteasy.version}
+
+
+```
+
+其中版本号对应为
+
+```xml
+4.1.9.Final
+3.9.0.Final
+```
+
+第二步需要在 接口添加 相关注解
-public @interface RPCInject {
-
- int port() default 0;
- int timeout() default -1;
- int retries() default -1;
- int actives() default -1;
- String group() default "";
- String version() default "";
- String loadbalance() default "";
- String async() default "";
- String check() default "";
+```java
+@Path("users") // #1
+@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) // #2
+@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
+public interface UserService {
+ @GET // #3
+ @Path("{id: \\d+}")
+ User getUser(@PathParam("id") Long id);
+
+ @POST // #4
+ @Path("register")
+ Long registerUser(User user);
}
-```
\ No newline at end of file
+```
+
+这部分的内容,可以添加在接口里,也可以添加在实现类里,具体参考:http://dubbo.apache.org/zh-cn/blog/dubbo-rest.html
+
+
+第三步,在 jboot.properties 添加 restful 协议:
+
+```properties
+jboot.rpc.dubbo.protocol.name = dubbo
+jboot.rpc.dubbo.protocol.host = 127.0.0.1
+jboot.rpc.dubbo.protocol.port = 28080
+
+jboot.rpc.dubbo.protocol.rest.name = rest
+jboot.rpc.dubbo.protocol.rest.host = 127.0.0.1
+jboot.rpc.dubbo.protocol.rest.port = 8080
+jboot.rpc.dubbo.protocol.rest.server = netty
+```
+
+第四步:给 Service 配置暴露协议
+
+
+```properties
+jboot.rpc.dubbo.provider.protocal = default,rest //使用 dubbo 和 rest 两种协议同时暴露
+jboot.rpc.dubbo.provider.default = true // 给应用配置默认的 provider
+```
+
+
+
+## 高级功能
+
+### 更多的 dubbo 配置
+
+目前,jboot 不支持通过 jboot.properties 直接对 service 和 reference 配置,但是可以对 provider 和 consumer 的配置,
+在通过 @RPCInject 来再次复制给 service 或者 reference,结果也等同于对 service 和 reference 配置进行配置了。
+
+
+#### application
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-application/
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.application`
+
+例如:
+
+```properties
+jboot.rpc.dubbo.application.name = xx.xx.xx
+jboot.rpc.dubbo.application.version = xx.xx.xx
+```
+
+
+#### registry
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-registry/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.registry`
+
+例如:
+
+```properties
+jboot.rpc.dubbo.registry.address = xx.xx.xx
+```
+
+> 例如使用 ncaos 注册中心时,配置内容如下:
+>
+> `jboot.rpc.dubbo.registry.address=nacos://127.0.0.1:8848?namespace=test`
+
+
+
+
+更多的注册中心配置内容如下:
+
+```properties
+jboot.rpc.dubbo.registry.address = xx.xx.xx
+jboot.rpc.dubbo.registry.port = xx.xx.xx
+jboot.rpc.dubbo.registry.username = xx.xx.xx
+jboot.rpc.dubbo.registry.password = xx.xx.xx
+```
+
+**多个注册中心**
+
+多注册中心可以配置如下(多 protocol 、多 consumer、多provider 都是同理):
+
+
+```properties
+jboot.rpc.dubbo.registry.address = xx.xx.xx
+jboot.rpc.dubbo.registry.port = xx.xx.xx
+jboot.rpc.dubbo.registry.username = xx.xx.xx
+jboot.rpc.dubbo.registry.password = xx.xx.xx
+
+
+jboot.rpc.dubbo.registry.other1.address = xx.xx.xx
+jboot.rpc.dubbo.registry.other1.port = xx.xx.xx
+jboot.rpc.dubbo.registry.other1.username = xx.xx.xx
+jboot.rpc.dubbo.registry.other1.password = xx.xx.xx
+
+
+jboot.rpc.dubbo.registry.other2.address = xx.xx.xx
+jboot.rpc.dubbo.registry.other2.port = xx.xx.xx
+jboot.rpc.dubbo.registry.other2.username = xx.xx.xx
+jboot.rpc.dubbo.registry.other2.password = xx.xx.xx
+```
+
+这样,在系统中就存在了多个注册中心,第一个配置的名称(name)分别为 default,第二个和第三个为
+other1、other2,这样,当一个服务(或者接口)需要在多个注册中心暴露的时候,只需要在其 registry 配置相应的 name 即可。
+
+例如:
+
+```properties
+jboot.rpc.dubbo.provider.address = default,other1
+```
+
+若当服务没有指定注册中心,注册中心默认为 default。
+
+
+#### protocol
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-protocol/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.protocol`
+
+例如:
+
+```properties
+jboot.rpc.dubbo.protocol.host = 127.0.0.1
+jboot.rpc.dubbo.protocol.port = 28080
+```
+
+
+
+#### provider
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-provider/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.provider`
+
+例如:
+
+```
+jboot.rpc.dubbo.provider.host = 127.0.0.1
+```
+
+
+
+#### consumer
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-consumer/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.consumer`
+
+例如:
+
+```
+jboot.rpc.dubbo.consumer.timeout = 127.0.0.1
+```
+
+
+
+#### monitor
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-monitor/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.monitor`
+
+例如:
+
+```
+jboot.rpc.dubbo.monitor.protocol = xxx
+```
+
+
+#### metrics
+
+
+对应的配置类: org.apache.dubbo.config.MetricsConfig
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.metrics`
+
+例如:
+
+```
+jboot.rpc.dubbo.metrics.protocol = xxx
+```
+
+
+#### module
+
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-module/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.module`
+
+例如:
+
+```
+jboot.rpc.dubbo.module.name = xxx
+```
+
+
+#### MetadataReport
+
+
+对应的配置类: org.apache.dubbo.config.MetadataReportConfig
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.metadata-report`
+
+例如:
+
+```
+jboot.rpc.dubbo.metadata-report.group = xxx
+```
+
+
+#### ConfigCenter
+
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-config-center/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.config-center`
+
+例如:
+
+```
+jboot.rpc.dubbo.config-center.group = xxx
+```
+
+#### method
+
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-method/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.method`
+
+例如:
+
+```
+jboot.rpc.dubbo.method.name = xxx
+```
+
+#### argument
+
+
+https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-argument/
+
+
+对应的 jboot 的配置前缀为: `jboot.rpc.dubbo.argument`
+
+例如:
+
+```
+jboot.rpc.dubbo.argument.name = xxx
+```
+
diff --git a/doc/docs/rpc2.x.md b/doc/docs/rpc2.x.md
new file mode 100644
index 0000000000000000000000000000000000000000..30116bc32ed3e49055405ec2851d1c6f50406705
--- /dev/null
+++ b/doc/docs/rpc2.x.md
@@ -0,0 +1,156 @@
+# RPC 远程调用
+
+## 目录
+
+- 配置
+- 开始使用
+- 高级功能
+
+## 配置
+在 Jboot 中,默认实现了对 Dubbo、motan 的 RPC 调用支持。在使用 RPC 远程调用之前,需要做一些基本的配置。
+
+
+例如 :
+```
+jboot.rpc.type = dubbo
+jboot.rpc.callMode = direct
+jboot.rpc.directUrl = 127.0.0.1:8000
+```
+
+- jboot.rpc.type : RPC 的类型,不配置默认为 Dubbo
+- jboot.rpc.callMode : RPC 的调用方式
+ - direct : 直联模式
+ - registry : 注册中心模式(服务注册和服务发现)
+- jboot.rpc.directUrl : 当 callMode 为 direct 直联模式时,需要配置直联模式的服务器 IP 地址和端口号。
+
+> 更多的配置请查看 [config.md](./config.md)
+
+## 开始使用
+
+一般情况下,RPC 调用需要以下几个步骤:
+
+- 1、定义接口
+- 2、编写实现类
+- 3、启动 Server 暴露服务
+- 4、启动客户端、通过 RPC 调用 Server 提供的服务
+
+
+**定义接口**
+
+```java
+
+public interface BlogService {
+
+ public String findById();
+ public List findAll();
+}
+```
+
+**编写实现类**
+
+```java
+
+
+@RPCBean
+public class BlogServiceProvider implements BlogService {
+
+ @Override
+ public String findById() {
+ return "id from provider";
+ }
+
+ @Override
+ public List findAll() {
+ return Lists.newArrayList("item1","item2");
+ }
+}
+```
+
+**启动 Server 暴露服务**
+
+```java
+
+public class DubboServer {
+
+ public static void main(String[] args) {
+
+ JbootApplication.run(args);
+ System.out.println("DubboServer started...");
+
+ }
+}
+```
+
+
+**启动客户端、通过 RPC 调用 Server 提供的服务**
+```java
+
+@RequestMapping("/dubbo")
+public class DubboClient extends JbootController{
+
+ public static void main(String[] args) {
+
+ //Undertow端口号配置
+ JbootApplication.setBootArg("undertow.port", "8888");
+
+ JbootApplication.run(args);
+ }
+
+
+ @RPCInject
+ private BlogService blogService;
+
+ public void index() {
+ System.out.println(blogService);
+ renderText("blogId : " + blogService.findById());
+ }
+
+}
+```
+
+
+## 高级功能
+
+在以上的示例中,使用到了两个注解:
+- @RPCBean,标示当前服务于RPC服务
+- @RPCInject,RPC注入赋值
+
+虽然以上示例中,`@RPCBean` 和 `@RPCInject` 没有添加任何的参数,但实际是他们提供了非常丰富的配置。
+
+以下分别是 `@RPCBean` 和 `@RPCInject` 的定义:
+
+RPCBean :
+```java
+
+public @interface RPCBean {
+
+ int port() default 0;
+ int timeout() default -1;
+ int actives() default -1;
+ String group() default "";
+ String version() default "";
+
+ //当一个Service类实现对个接口的时候,
+ //可以通过这个排除不暴露某个实现接口
+ Class[] exclude() default Void.class;
+}
+```
+
+
+RPCInject :
+
+```java
+
+public @interface RPCInject {
+
+ int port() default 0;
+ int timeout() default -1;
+ int retries() default -1;
+ int actives() default -1;
+ String group() default "";
+ String version() default "";
+ String loadbalance() default "";
+ String async() default "";
+ String check() default "";
+}
+```
\ No newline at end of file
diff --git a/doc/docs/schedule.md b/doc/docs/schedule.md
index 6ff2bada49dff8d0bc35b7ea3ac7e72e45640ad8..5c09c61a07f0f0a4e396d8d50715731d070b6fac 100644
--- a/doc/docs/schedule.md
+++ b/doc/docs/schedule.md
@@ -53,6 +53,8 @@ public class MyTask implements Runnable {
}
}
```
+注意:由于 jdk `ScheduledThreadPoolExecutor` 自身实现的问题,任务的 run 方法如果抛出异常,会造成线程池停止调度,
+请务必在任务的 run 方法中使用 try catch 自行捕捉异常。
**方案3:**
使用 JFinal 自带的任务调度方案,参考文档:https://www.jfinal.com/doc/9-1
diff --git a/doc/docs/sentinel.md b/doc/docs/sentinel.md
new file mode 100644
index 0000000000000000000000000000000000000000..193edac687c39d5da84a1fb0b2356263b789f237
--- /dev/null
+++ b/doc/docs/sentinel.md
@@ -0,0 +1,171 @@
+# Sentinel 限流
+
+## 目录
+
+- 概述
+- sentinel 的使用
+
+## 概述
+随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
+
+Sentinel 具有以下特征:
+
+- **丰富的应用场景**:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
+- **完备的实时监控**:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
+- **广泛的开源生态**:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
+- **完善的 SPI 扩展点**:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
+
+
+
+Sentinel 分为两个部分:
+
+核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
+控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
+
+
+更多的文档请参考:
+
+https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
+
+
+## sentinel 的使用
+
+**第一步,启动 Sentinel dashboard:**
+
+下载 Sentinel 的 jar 到本地,并通过如下方式启动 启动 sentinel dashboard
+
+```shell
+java -jar sentinel-dashboard-1.8.0.jar
+```
+
+ jar 的下载地址:https://github.com/alibaba/Sentinel/releases
+
+启动时,默认端口号为:8080,可以通过 -Dserver.port=8888 用于指定 Sentinel 控制台端口为 8888。
+
+例如:
+
+```shell
+java -Dserver.port=8888 -jar sentinel-dashboard-1.8.0.jar
+```
+
+从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
+
+可以通过如下配置来修改掉默认的账号和密码:
+- -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel;
+- -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;
+
+关于控制台的更多配置,请参考:
+https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0#%E6%8E%A7%E5%88%B6%E5%8F%B0%E9%85%8D%E7%BD%AE%E9%A1%B9
+
+**第二步:配置项目的 jboot.properties 、 sentinel.properties 和 Maven 依赖**
+
+在 jboot.properties 添加如下配置
+
+```properties
+jboot.sentinel.enable = true;
+
+// 是否对 http 请求启用限流,默认值为 true,启用后还需要去 sentinel 后台配置
+jboot.sentinel.reqeustEnable = true;
+
+// 配置 sentinel 的前缀,多个用英文逗号隔开,若不配置则所有的都会被拦截
+jboot.sentinel.reqeustTargetPrefix = "/my";
+
+// 如果 http 被限流后跳转的页面
+jboot.sentinel.requestBlockPage;
+
+ // 如果 http 被限流后渲染的 json 数据,requestBlockPage 配置优先于此项
+jboot.sentinel.requestBlockJsonMap;
+```
+
+在项目的 resource 目录下创建 sentinel.properties 文本,并配置相关信息如下:
+
+```
+csp.sentinel.dashboard.server=localhost:8080
+```
+这个配置指的是 sentinel 配置服务器的地址
+
+关于更多 sentinel.properties 的配置请参考:
+
+https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9#%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE%E9%A1%B9
+
+
+添加 maven 依赖:
+
+
+```xml
+
+ com.alibaba.csp
+ sentinel-core
+ ${sentinel.version}
+
+
+
+ com.alibaba.csp
+ sentinel-cluster-client-default
+ ${sentinel.version}
+
+
+
+ com.alibaba.csp
+ sentinel-transport-simple-http
+ ${sentinel.version}
+
+```
+
+如果使用 阿里云 AHAS 替代 sentinel dashboard,需要添加如下依赖(以上依赖不再需要):
+
+
+```xml
+
+ com.alibaba.csp
+ ahas-sentinel-client
+ 1.8.0
+
+```
+
+阿里云 AHAS 截图:
+
+
+
+更多关于 阿里云 AHAS 的文档请参考:https://github.com/alibaba/Sentinel/wiki/AHAS-Sentinel-%E6%8E%A7%E5%88%B6%E5%8F%B0
+
+
+
+**第三步:配置限流资源**
+
+在项目的任意目录下,使用注解 `@SentinelResource` 给方法进行配置,代码如下:
+
+```java
+@RequestMapping("/sentinel")
+public class SentinelController extends JbootController {
+
+ @SentinelResource
+ public void index(){
+ renderText("sentinel index...");
+ }
+}
+```
+
+或者在 Service 中
+
+```java
+public class UserService{
+
+ // 原本的业务方法.
+ @SentinelResource(blockHandler = "blockHandlerForGetUser")
+ public User getUserById(String id) {
+ throw new RuntimeException("getUserById command failed");
+ }
+
+ // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
+ public User blockHandlerForGetUser(String id, BlockException ex) {
+ return new User("admin");
+ }
+}
+```
+
+然后通过浏览器进入 sentinel dashboard 中心,对 `SentinelController.index()` 和 `UserService.getUserById()` 的资源进行具体的限流参数配置。
+
+关注注解 `@SentinelResource` 更多的配置,请参考文档: https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81#sentinelresource-%E6%B3%A8%E8%A7%A3
+
+更多的 sentinel dashboard 配置内容请参考:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
\ No newline at end of file
diff --git a/doc/docs/serialize.md b/doc/docs/serialize.md
index 8f33c7dde99a50a574d5d011c1f258c8e3b2fd60..75cebf694946b23b8d593866d18f78add020f0fc 100644
--- a/doc/docs/serialize.md
+++ b/doc/docs/serialize.md
@@ -1,14 +1,22 @@
# 序列化
-在jboot中,很多分布式的调用,需要把 javaBean 序列化后才能进行传输 或者 进行缓存。比如 rpc、mq、cache 等都需要序列化组件。
+序列化 (Serialization) 是将 Java 对象转换为可以存储或传输的状态信息。在很多的场景下,比如缓存、分布式调用 RPC,MQ 等都需要到了序列化。才能把 Java 对象传输到另一个其他系统。
-例如,在 redis 中,我们可以为 redis 组件自定义自己的序列化方式,只需要进入如下配置即可:
+在 Jboot 中,已经内置了多种序列化解决方案。
-```
-jboot.redis.serializer = xxxx
+- fst
+- kryo
+- fastjson
+
+默认已经使用了 FST,当没有特殊需求的时候,使用默认的 fst 就可以了,但是在某些情况下,比如 redis 缓存已经使用了其他序列化方案进行存储数据了,我们要正确读取其数据,需要设置我们的序列化方案为 redis 已经使用的方案。
+
+此时,我们可以通过如下的配置,来修改掉 redis 的序列化:
+
+```properties
+jboot.redis.serializer = xxx
```
-自定义序列化是通过Jboot的SPI机制来实现的,我们只需要安装 SPI 规范来实现自己的序列化组件,然后通过以上配置即可。
+其中,xxx 是序列化的名称,倘若 Jboot 中不存在此序列化方案,需要用户自行通过 Jboot SPI 进行扩展,
-更多关于 SPI 的查看 [这里](.spi.md) 。
+更多关于 SPI 的查看 [这里](./spi.md) 。
diff --git a/doc/docs/shiro.md b/doc/docs/shiro.md
index b06fa86257453b889be2a7736c6b939e2f6c9bb0..45e5462a20f7748560275142b63b99a2916bde21 100644
--- a/doc/docs/shiro.md
+++ b/doc/docs/shiro.md
@@ -19,25 +19,24 @@ Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、
Jboot 默认情况下并没有依赖 shiro,因此,在在使用 Jboot 的 Shiro 模块之前,需要你添加下 Shiro 的 Maven 依赖。
```xml
-
- org.apache.shiro
- shiro-core
- 1.3.2
+
+ org.apache.shiro
+ shiro-core
+ 1.7.1
- org.apache.shiro
- shiro-web
- 1.3.2
+ org.apache.shiro
+ shiro-web
+ 1.7.1
```
-注意:目前暂时不支持 Shiro 1.4.x 版本,晚点会添加支持。
同时,需要在 resources 目录下配置上您的 `shiro.ini` 配置文件,并在 `jboot.porperties` 添加上 `jboot.shiro.ini = shiro.ini` 配置。在 `shiro.ini` 文件里,需要在自行扩展 `realm` 等信息。
## Shiro的使用
-Jboot 的 Shiro 模块为您提供了以下12个模板指令,同时支持 Shiro 的5个 Requires 注解功能。方便您使用 Shiro。
+Jboot 的 Shiro 模块为您提供了以下12个模板指令,同时支持 Shiro 的 5 个 Requires 注解功能。方便您使用 Shiro。
**12个Html模板指令**
- shiroAuthenticated:用户已经身份验证通过,Subject.login登录成功
@@ -196,7 +195,7 @@ public class MyController extends JbootController{
}
```
-RequiresUser、RequiresGuest、RequiresAuthentication的使用
+RequiresUser、RequiresGuest、RequiresAuthentication 的使用
```java
public class MyController extends JbootController{
@@ -233,12 +232,12 @@ public class MyshiroListener implements JbootShiroInvokeListener {
@Override
- public void onInvokeBefore(FixedInvocation inv) {
+ public AuthorizeResult onInvokeBefore(Invocation inv) {
//do nothing
}
@Override
- public void onInvokeAfter(FixedInvocation inv, AuthorizeResult result) {
+ public void onInvokeAfter(Invocation inv, AuthorizeResult result) {
//说明该用户授权成功,
//可以允许访问
@@ -280,4 +279,12 @@ jboot.shiro.invokeListener=com.xxx.MyshiroListener
### Shiro 与 Jwt 整合
-### Shiro 与 SSO 整合
\ No newline at end of file
+ 在 `JbootShiroInvokeListener.onInvokeBefore()` 中,接收 JWT 数据,并 JWT 处理的相关认证,然后返回
+ `AuthorizeResult`,返回的 `AuthorizeResult` 直接交给 `onInvokeAfter` 处理,不再交给
+Shiro 内部处理。
+
+若 `JbootShiroInvokeListener.onInvokeBefore()` 返回 null,则交给 Shiro 内部处理
+
+### Shiro 与 SSO 整合
+
+同 Jwt 处理方案。
\ No newline at end of file
diff --git a/doc/docs/spi.md b/doc/docs/spi.md
index 9f3e3bf1f8e7212d54421e054ad86158c71298a5..9cac715a170242458e3b992a3a097390815110d7 100644
--- a/doc/docs/spi.md
+++ b/doc/docs/spi.md
@@ -15,7 +15,7 @@ SPI 的全名为 : Service Provider Interface。
当服务的提供者,提供了服务接口的一种实现之后,在 jar 包的`META-INF/services/` 目录里同时创建一个以 **服务接口** 命名的文件。该文件里就是实现该服务接口的具体实现类。而 Jboot 装配这个模块的时候,就能通过该 jar 包 `META-INF/services/` 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
## Jboot SPI 模块
-在jboot中,一下模块已经实现了SPI机制。
+在jboot中,以下模块已经实现了SPI机制。
- Jbootrpc
- JbootHttp
@@ -34,4 +34,4 @@ SPI 的全名为 : Service Provider Interface。
- 3:通过在jboot.properties文件中配置上类型为 mycache,配置代码如下:`jboot.cache.type = mycache`
-通过以上三步,我们就可以完成了对 JbootCache 模块的扩展,其他模块类似。
\ No newline at end of file
+通过以上三步,我们就可以完成了对 JbootCache 模块的扩展,其他模块类似。
diff --git a/doc/docs/start.md b/doc/docs/start.md
new file mode 100644
index 0000000000000000000000000000000000000000..1aa9efb9b96ee5322c9e8d0913766a84dcf41a07
--- /dev/null
+++ b/doc/docs/start.md
@@ -0,0 +1,845 @@
+---
+sidebar: false
+---
+
+# 开始使用Jboot
+
+Jboot 使用到了如下的技术,了解 Jboot 之前,请先保证您已经了掌握如下技术:
+
+ * 熟悉 Java 编程语言
+ * 熟悉 maven 的基本原理
+ * 熟悉 IntelliJ IDEA 或者 Eclipse 等编辑器的使用
+
+交流 QQ 群:
+* 群1:601440615 (已满)
+* 群2:719614554 (开放中)
+
+## 创建项目
+
+### 通过 IntelliJ IDEA 创建项目
+
+IntelliJ IDEA 下载地址:https://www.jetbrains.com/idea/ ,下载完成后完成后开始安装,安装过程略。
+
+第一步:打开 IntelliJ IDEA 创建 maven 项目,如下图:
+
+
+第二步:填写 maven 项目的 GroupId、ArtifactId 和 Version
+
+* GroupId 一般是包名,用来做项目的唯一标识
+* ArtifactId 一般是项目名
+* Version 是项目的版本
+
+
+第三步:填写 项目存储路径
+
+
+
+创建完毕后,我们会看到如下图所示,注意点击 Enable Auto-Import.
+
+
+
+### 通过 Eclipse 创建项目
+略,和 通过 IntelliJ IDEA 创建项目 基本相同。
+
+## Maven 依赖
+
+通过 以上步骤建立项目后,我们会在项目目录下找到 pom.xml 文件,这个文件是 maven 的核心文件,maven 是通过 pom.xml 对项目进行依赖配置和管理的。
+
+我们需要在 pom.xml 里添加对 Jboot 的依赖配置,如下代码:
+
+```xml
+
+ io.jboot
+ jboot
+ 4.1.0
+
+```
+
+如下图所示:
+
+
+
+## Hello World
+
+一般情况下,对一个新项目的了解是从 Hello World 开始的,因此,我们需要通过 Jboot 来写一个 Hello World 程序。
+
+这个 Hello World 的需求是:
+
+> **通过编写代码,我们在浏览器访问后输出 “Hello World Jboot” 的文字内容。**
+
+通过以上步骤,我们创建好了项目、添加好了 jboot 的maven依赖,接下来我们需要来创建一个叫 IndexController 的java文件
+
+
+
+
+
+IndexController 的代码如下:
+
+```java
+import io.jboot.app.JbootApplication;
+import io.jboot.web.controller.JbootController;
+import io.jboot.web.controller.annotation.RequestMapping;
+
+@RequestMapping("/")
+public class IndexController extends JbootController {
+
+ public void index() {
+ renderText("Hello World Jboot");
+ }
+
+
+ public static void main(String[] args) {
+ JbootApplication.run(args);
+ }
+}
+```
+
+以上代码需要注意以下几点:
+1. IndexController 需要继承 JbootController 或者 Controller
+1. 需要添加 @RequestMapping("/") 请求映射配置
+1. 通过编写 `index()` 方法来接收请求,并向浏览器输出 "Hello World Jboot" , 此处注意:必须叫 `index()` 不能修改名字,后续会讲到其原理。
+
+通过运行 IndexController 的 main() 方法,我们可以看到如下的日志输出:
+
+```
+
+ ____ ____ ___ ___ ______
+ | || \ / \ / \ | |
+ |__ || o )| || || |
+ __| || || O || O ||_| |_|
+/ | || O || || | | |
+\ ` || || || | | |
+ \____||_____| \___/ \___/ |__|
+
+
+
+JbootApplication { mode='dev', version='4.1.0', jfinalConfig='io.jboot.core.JbootCoreConfig' }
+Classpath : /Users/michael/git/jboot/target/test-classes/
+Starting JFinal 4.2 -> http://127.0.0.1:8080
+Info: jfinal-undertow 1.6, undertow 2.0.19.Final, jvm 1.8.0_201
+ClassScanner scan classpath : /Users/michael/git/jboot/target/test-classes
+ClassScanner scan classpath : /Users/michael/git/jboot/target/classes
+Starting Complete in 1.3 seconds. Welcome To The JFinal World (^_^)
+
+JbootResourceLoader started, Watched resource path name : webapp
+```
+
+我们看到最后一行日志的内容是:
+
+```
+server started success , url : http://127.0.0.1:8080/
+```
+此时我们通过浏览器访问:http://127.0.0.1:8080 , 就可以看到如下内容:
+
+
+
+
+
+## 链接数据库
+
+在 Java Web 开发中,几乎 99% 以上的项目都需要和数据库打交道,因此,了解 Jboot 如何连接数据成为了必须。
+
+通过 Jboot 连接数据库只需要做两步:
+1. 创建 jboot.properties 配置文件
+1. 在 jboot.properties 添加数据库连接信息
+
+第一步:在项目的resource目录下创建 jboot.properties 文件,此时,项目的目录结构应该如下:
+
+```
+├── pom.xml
+├── src
+│ ├── main
+│ │ ├── java
+│ │ │ └── IndexController.java
+│ │ └── resources
+│ │ └── jboot.properties #注意文件目录不要错了
+│ └── test
+│ └── java
+```
+
+第二步:在 jboot.properties 文件添加如下数据库信息:
+
+```
+jboot.datasource.type = mysql
+jboot.datasource.url = jdbc:mysql://127.0.0.1:3306/jbootdemo
+jboot.datasource.user = root
+jboot.datasource.password =
+```
+
+* jboot.datasource.type:配置的是数据库的类型,目前 Jboot 支持的数据库类型有:Mysql 、Oracle 、SqlServer 、Postgresql 、Sqlite 和 其他标准的数据库。
+* jboot.datasource.url: 配置的是数据库的连接信息
+* jboot.datasource.user: 配置的是数据库的连接账号
+* jboot.datasource.password: 配置的是数据库的连接密码,没有密码可以留空
+
+通过配置完毕后,Jboot就已经有了访问数据库的能力,我们可以在 IndexController 写一个 `dbtest()` 方法,来测试下 Jboot 的数据库访问能力,代码如下:
+
+```java
+@RequestMapping("/")
+public class IndexController extends JbootController {
+
+ public void index() {
+ renderText("Hello World Jboot");
+ }
+
+ public void dbtest(){
+ List records = Db.find("select * from user");
+ renderText(Arrays.toString(records.toArray()));
+ }
+
+
+ public static void main(String[] args) {
+ JbootApplication.run(args);
+ }
+}
+```
+注意:以上代码能够正式运行的前提是:
+1. 你本地安装好mysql数据库,并创建好库 `jbootdemo` ,因为数据库的连接url是:`jdbc:mysql://127.0.0.1:3306/jbootdemo`
+2. jbootdemo 下要有数据表 user,因为 sql 查询内容是:`select * from user`
+
+例如:作者本地数据库的内容如下:
+
+
+
+运行 IndexController 的 `main()` 方法,并访问 `http://127.0.0.1:8080/dbtest`,会看到如下内容所示:
+
+
+
+此时,证明 Jboot 已经能够准确访问数据库。
+
+
+
+## 使用代码生成器
+Jboot 内置了一个简易的代码生成器,通过代码生成器运行,Jboot帮开发者生成每个数据库表对应 java 的 model 实体类,同时可以生成带有增、删、改、查基本数据库操作能力的 service 层代码。
+
+在使用 Jboot 代码生成器之前,我们需要在 jboot.properties 配置好数据库的连接信息(以上内容已经提到)。并编写任意名字带有`main()`方法的执行类,例如我们叫 CodeGenerator, 代码如下:
+
+```java
+public class CodeGenerator {
+
+ public static void main(String args[]){
+
+ // 配置数据库的数据源
+ JbootApplication.setBootArg("jboot.datasource.url", "jdbc:mysql://127.0.0.1:3306/jbootdemo");
+ JbootApplication.setBootArg("jboot.datasource.user", "root");
+ JbootApplication.setBootArg("jboot.datasource.password", "123456");
+
+ String modelPackage = "io.jboot.test.codegen.model";
+ String baseModelPackage = modelPackage + ".base";
+
+ String modelDir = CodeGenHelpler.getUserDir() + "/src/main/java/" + modelPackage.replace(".", "/");
+ String baseModelDir = CodeGenHelpler.getUserDir() + "/src/main/java/" + baseModelPackage.replace(".", "/");
+
+ System.out.println("start generate...");
+ System.out.println("generate dir:" + modelDir);
+
+ // 生成 Model
+ new JbootBaseModelGenerator(baseModelPackage, baseModelDir).setGenerateRemarks(true).generate();
+ new JbootModelGenerator(modelPackage, baseModelPackage, modelDir).generate();
+
+
+ String servicePackage = "io.jboot.test.codegen.service";
+ String serviceImplPackage = "io.jboot.test.codegen.service.impl";
+
+ String serviceOutputDir = CodeGenHelpler.getUserDir() + "/src/main/java/" + servicePackage.replace(".", "/");
+ String serviceImplOutputDir = CodeGenHelpler.getUserDir() + "/src/main/java/" + serviceImplPackage.replace(".", "/");
+
+ // 生成 Service 接口 及其 实现类
+ new JbootServiceInterfaceGenerator(servicePackage, serviceOutputDir, modelPackage).generate();
+ new JbootServiceImplGenerator(servicePackage, serviceImplPackage, serviceImplOutputDir, modelPackage).setImplName("impl").generate();
+
+
+ }
+}
+```
+
+运行 CodeGenerator 的 `main()` 方法之后,我们能看到 Jboot 已经帮我们创建好对应的包名和类名,此时,项目的目录如下:
+
+```
+├── pom.xml
+├── src
+│ ├── main
+│ │ ├── java
+│ │ │ ├── CodeGenerator.java
+│ │ │ ├── IndexController.java
+│ │ │ └── com
+│ │ │ └── xxx
+│ │ │ ├── model
+│ │ │ │ ├── Article.java
+│ │ │ │ ├── User.java
+│ │ │ │ └── base
+│ │ │ │ ├── BaseArticle.java
+│ │ │ │ └── BaseUser.java
+│ │ │ └── service
+│ │ │ ├── ArticleService.java
+│ │ │ ├── UserService.java
+│ │ │ └── impl
+│ │ │ ├── ArticleServiceImpl.java
+│ │ │ └── UserServiceImpl.java
+│ │ └── resources
+│ │ └── jboot.properties
+│ └── test
+│ └── java
+```
+
+通过 Jboot 代码生成器的运行,项目对应的 model 类和 service 会自动生成,同时 Service 层的代码以及带有了对数据库增、删、改、查的基本能力.
+
+需要注意的是:
+**再次运行该代码生成器的时候,BaseUser、BaseArticle会被重新覆盖,其他代码不会被覆盖。** 若需要重新生成 service 层 和 User、Article 等代码,需要手动删除后,再次运行代码生成器 CodeGenerator 。
+
+## 自动注入
+Jboot 通过 Google Guice 提供了强健稳定的代码注入功能,使用注入功能只需要了解以下三个注解:
+1. @Bean : 声明此类可以被自动注入
+2. @Inject : 对属性进行赋值注入
+
+通过代码生成器生成的Service层代码就已经默认添加上了 @Bean 和 @Singleton 这两个配置,生成的代码如下:
+
+```java
+package com.xxx.service.impl;
+
+import io.jboot.aop.annotation.Bean;
+import com.xxx.service.UserService;
+import com.xxx.model.User;
+import io.jboot.service.JbootServiceBase;
+
+import javax.inject.Singleton;
+
+@Bean
+public class UserServiceImpl extends JbootServiceBase implements UserService {
+
+}
+```
+
+我们使用到 UserService 接口的时候,只需要添加 @Inject 注解即可,例如:在 IndexController 需要用到 UserService,代码如下:
+
+```java
+@RequestMapping("/")
+public class IndexController extends JbootController {
+
+ @Inject
+ private UserService userService;
+
+ public void index() {
+ renderText("Hello World Jboot");
+ }
+
+ public void dbtest() {
+ List records = Db.find("select * from user");
+ renderText(Arrays.toString(records.toArray()));
+ }
+
+
+ public void users() {
+ // 这里用到了 userService 的查询方法
+ List users = userService.findAll();
+ renderText(Arrays.toString(users.toArray()));
+ }
+
+
+ public static void main(String[] args) {
+ Jboot.run(args);
+ }
+}
+```
+
+运行 `main()` 方法后,我们通过浏览访问 `http://127.0.0.1:8080/users` ,此时,页面显示的内容和 访问 `http://127.0.0.1:8080/dbtest` 的效果是一样的:
+
+
+
+
+
+## 数据库的增删改查
+在本章节,我们要完成一个小型的项目,这个项目是一个用户管理系统,他具有以下功能:
+
+* 显示用户列表,带有分页的功能
+* 可以对单个用户删除
+* 可以对用户进行修改
+* 可以添加新的用户
+
+### 分页查询
+
+我们可以继续来改造 IndexController,通过修改代码生成器的生成的 UserService,来达到上述要求的功能。
+
+在上述的章节里,我们知道,通过如下的代码可以获得所有的用户信息:
+
+```java
+ public void users() {
+ List users = userService.findAll();
+ renderText(Arrays.toString(users.toArray()));
+}
+```
+
+如果要分页,我们需要在UserService添加一个分页的方法,并在 UserServiceImpl 来实现这个分页的方法,代码如下:
+
+UserService.java
+
+```java
+public interface UserService {
+
+ // 代码生成器生成的其他方法略
+ //...
+
+ public Page paginate(int page, int size);
+}
+```
+
+UserServiceImpl.java
+
+```java
+@Bean
+public class UserServiceImpl extends JbootServiceBase implements UserService {
+
+ public Page paginate(int page, int size) {
+ return DAO.paginate(page, size);
+ }
+}
+```
+
+为了代码更加简洁直观,我们新建一个 UserController 来做用户相关的增删改查功能,代码如下:
+
+```java
+@RequestMapping("/user")
+public class UserController extends JbootController {
+
+ @Inject
+ private UserService userService;
+
+ public void index() {
+ int page = getParaToInt("page", 1);
+ Page userPage = userService.paginate(page, 10);
+ setAttr("pageData", userPage);
+ render("/user.html");
+ }
+
+}
+```
+
+* `getParaToInt()` 可以获得request提交过来的page数据,例如:http://127.0.0.1:8080/user?page=100 ,此时,代码里 page 的值是 100,当不传值得时候,默认值是 1 。
+* 通过 userService.paginate 查询数据库,返回一个 Page 对象,里面包含了 当前页码、总页码 和 数据列表等信息。
+* 通过 `setAttr()` 把数据结果传递到页面
+
+把数据传递到 user.html 后,需要 user.html 把具体的数据和分页相关在网页上列出来。
+
+第一步:完善数据的显示,user.html内容如下:
+
+```html
+
+
+
+
+ user index
+
+
+
+
+ ID |
+ 登录名 |
+ 密码 |
+
+ #for(user : pageData.list )
+
+ #(user.id) |
+ #(user.login_name) |
+ #(user.password) |
+
+ #end
+
+
+
+```
+此时,运行 `main()` 方法,访问 `http://127.0.0.1:8080/user` ,页面显示内容如下:
+
+
+
+第二步:完善分页功能。
+
+Jboot应用的分页功能需要自定义一个分页标签,自定义分页标签非常简单,代码内容如下:
+
+```java
+@JFinalDirective("myPaginate")
+public class MyPaginateDirective extends JbootPaginateDirective {
+
+}
+```
+然后再修改 user.html 内容如下:
+
+```html
+
+
+
+
+ user index
+
+
+
+
+ ID |
+ 登录名 |
+ 密码 |
+
+ #for(user : pageData.list )
+
+ #(user.id) |
+ #(user.login_name) |
+ #(user.password) |
+
+ #end
+
+#myPaginate()
+ #for(page : pages)
+ #(page.text??)
+ #end
+#end
+
+
+```
+
+此时,运行 `main()` 方法,访问 `http://127.0.0.1:8080/user` ,页面显示内容如下:
+
+
+
+由于数据量太小,同时在我们的代码里,要求每页显示10条数据,所以页面才显示了第一页,当我们在数据库添加数据量超过10条的时候,页面显示内容如下:
+
+
+
+同时,上一页、下一页等功能正常使用,如下图:
+
+
+
+实际上,#myPaginate() 自定义分页标签还可以做更多的配置,包括功能和样式等,但是这不是本章节要讨论的内容了。
+
+### 新增功能
+为了实现新增功能,我们需要写一个叫 add.html 的页面,并写对应的 Controller,保证可以访问。
+
+add.html 的代码如下:
+
+```html
+
+
+
+
+ user add
+
+
+
+
+
+```
+
+通过 add.html 内容我们能看到,当用户点击 `提交数据` 按钮的时候,页面会把数据提交到 `/user/doSave` 这个路径上去,所以,需要我们在 UserController 编写一个叫做 `doSave()` 的方法来接收数据,并保存到数据库。
+
+doSave() 方法内容如下:
+
+```java
+public void doSave() {
+ String loginName = getPara("login_name");
+ String password = getPara("password");
+
+ User user = new User();
+ user.setLoginName(loginName);
+ user.setPassword(password);
+
+ user.save();
+
+ redirect("/user");
+}
+```
+
+`doSave()` 方法的主要作用是接收数据、把数据保存到数据库、然后跳转到 `/user` 这个页面去。
+
+### 修改功能
+
+为了减少代码量,我们直接把 add.html 改造成为可用做新增,也可以用作修改的功能(通常在商业项目中也会这么做),因此,我们需要简单修改下 add.html 代码和 add() 这个方法的代码。
+
+add.html
+
+```html
+
+
+
+
+ user add
+
+
+
+
+
+```
+
+和新增功能的html对比,增加了 `` 这行代码。
+
+add() 方法内容如下:
+
+```java
+public void add() {
+ int id = getParaToInt("id", 0);
+ if (id > 0) { //有id ,说明有数据提交过来,用来做修改的标识。
+ setAttr("id", id);
+ }
+ render("/add.html");
+}
+```
+
+同时, doSave()方法也需要修改下,用来区分是新增还是修改,代码如下:
+
+```java
+public void doSave() {
+ String loginName = getPara("login_name");
+ String password = getPara("password");
+
+ long id = getParaToLong("id",0l);
+
+
+ User user = new User();
+ user.setLoginName(loginName);
+ user.setPassword(password);
+
+ if (id > 0){ //说明是更新
+ user.setId(id);
+ user.update();
+ }else { //说明是新增
+ user.save();
+ }
+
+ redirect("/user");
+}
+```
+
+最后,我们在改造下 user.html ,在表格的后面添加一个 `修改` 的连接, user.html 代码如下:
+
+```html
+
+
+
+
+ user index
+
+
+
+
+ ID |
+ 登录名 |
+ 密码 |
+ 操作 |
+
+ #for(user : pageData.list )
+
+ #(user.id) |
+ #(user.login_name) |
+ #(user.password) |
+ 修改 |
+
+ #end
+
+#myPaginate()
+ #for(page : pages)
+ #(page.text??)
+ #end
+#end
+
+
+```
+
+此时,页面内容如下,修改功能正常使用。
+
+
+
+### 删除功能
+
+删除功能更加简单,只需要在Controller接收ID,然后调用 userService.delete() 方法就可以了,改造 user.html 代码如下:
+
+```html
+
+
+
+
+ user index
+
+
+
+
+ ID |
+ 登录名 |
+ 密码 |
+ 操作 |
+
+ #for(user : pageData.list )
+
+ #(user.id) |
+ #(user.login_name) |
+ #(user.password) |
+
+ 修改
+ 删除
+ |
+
+ #end
+
+#myPaginate()
+ #for(page : pages)
+ #(page.text??)
+ #end
+#end
+
+
+```
+
+页面显示如下:
+
+
+
+我们只需要在 UserController 编写一个 del() 方法,接收id、删除数据库数据,并跳转回 /user 即可完成任务,代码如下:
+
+```java
+ public void del() {
+ long id = getParaToLong("id",0l);
+ userService.deleteById(id);
+ redirect("/user");
+}
+```
+
+到目前为止,增删改查所有功能完成。
+
+
+## 使用缓存提高性能
+通过以上内容,我们可以使用Jboot开发一个具有增、删、改、查基本功能的Java Web 应用,但是,在互联网的应用里,高并发的要求可以说是必不可少的,缓存在提高应用性能和并发上有绝对的话语权。
+
+在 Jboot 里,我们如何来使用缓存呢?
+
+Jboot 提供了两种方案:
+
+1. 注解
+1. 手写代码
+
+在注解中,Jboot提供了4个注解,方便的对缓存进行操作,他们分别是:
+
+* @Cacheable
+* @CachePut
+* @CacheEvict
+* @CachesEvict
+
+如何来使用呢?
+
+在以上的章节里,我们知道,如下的代码是一个分页查询的功能:
+
+```java
+public class UserServiceImpl extends JbootServiceBase implements UserService {
+
+ public Page paginate(int page, int size) {
+ return DAO.paginate(page, size);
+ }
+}
+```
+
+如何来让 `paginate(int page, int size)` 方法具有缓存的功能呢?
+
+非常简单:
+
+```java
+public class UserServiceImpl extends JbootServiceBase implements UserService {
+
+ @Cacheable(name = "myCache",key = "page:#(page)-#(size)")
+ public Page paginate(int page, int size) {
+ return DAO.paginate(page, size);
+ }
+}
+```
+只需要添加 `@Cacheable(name = "myCache",key = "page:#(page)-#(size)")` 这个注解。
+
+在Jboot中,默认的缓存为 `EhCache` , 这个注解的含义是:
+
+* 在EhCache中创建一个缓存为myCache的缓存区
+* 当查询第 1 页的时候,缓存的key为:`page:1-10`,因为 `paginate(int page, int size)` 方法在执行的时候,传递过来的值分别是:page=1,size=10
+* 当查询第 2 页的时候,缓存的key为:`page:2-10`,原因同上。
+
+当 `paginate(int page, int size)` 方法使用 @Cacheable 缓存之后,只有第一次访问的时候去查询数据库,之后的访问会直接从缓存中获取数据,大大提高了性能。
+
+但是...
+
+使用缓存也会带来一些问题,因为 `paginate(int page, int size)` 方法不再访问数据库,从而导致我们在数据库对数据进行增、删、改,这个页面数据都不会再发生变化。
+
+要让 `paginate(int page, int size)` 方法与数据库同步,怎么办呢?
+
+需要我们在对数据库进行 增、删、改 的时候,清除这个方法里的缓存数据。
+
+代码如下:
+
+```java
+public class UserServiceImpl extends JbootServiceBase implements UserService {
+
+ @Cacheable(name = "myCache",key = "page:#(page)-#(size)")
+ public Page paginate(int page, int size) {
+ return DAO.paginate(page, size);
+ }
+
+ @Override
+ @CacheEvict(name = "myCache",key = "*")
+ public boolean save(User model) {
+ return super.save(model);
+ }
+
+ @Override
+ @CacheEvict(name = "myCache",key = "*")
+ public boolean update(User model) {
+ return super.update(model);
+ }
+
+ @Override
+ @CacheEvict(name = "myCache",key = "*")
+ public boolean delete(User model) {
+ return super.delete(model);
+ }
+}
+```
+重写父类 `JbootServiceBase` 的增、删、改的方法,在这些方法添加 `@CacheEvict(name = "myCache",key = "*")` 注解。
+
+**被添加@CacheEvict的方法,在执行之后,会清除 name 为 myCache 的所有key。** 也就是清除 `paginate(int page, int size)` 方法所有的 key 。
+
+这样,就做到了 `paginate(int page, int size)` 方法与数据库同步的功能了。是不是非常简单呢 ?!
+
+不过 ...
+
+以上,只是 Jboot 缓存功能的冰山一角,Jboot 的缓存非常强大,比如:
+
+ * @CachePut 和 @CachesEvict 又如何使用?什么场景下使用?
+ * 如何使用 Redis 或者其他的缓存方案代替默认的 EhCache ,甚至是公司自己内部的缓存方案 ?
+ * 分布式缓存如何做 ?
+ * 如何设置缓存的失效时间 ?
+ * 如何做到整个网页缓存,类似页面静态化?
+ * 等等等等
+
+Jboot 都给与类非常完美的支持。
+
+## 探索 Jboot 的更多功能...
+
+恭喜你,到目前为止,你已经掌握了使用 Jboot 来开发一个 java web 的基本技能,包含了
+
+* MVC
+* ORM
+* AOP
+* 代码生成器
+* 使用缓存提高性能
+* 等等
+
+这是非常重要的一步。
+
+但是,Jboot 的功能远远不止这些,以上只是 Jboot 的冰山一角。
+
+Jboot 真正的核心是做微服务的开发,微服务的底层代码是分布式调用 RPC,RPC 的框架和实现的方案非常繁杂,不过 Jboot 已经支持了主流 RPC 的实现,其中包含了 Dubbo 、motan、Zbus 等, 未来还会添加 gRPC 等更多的支持。
+
+* 在 RPC 下,Jboot 支持了 RPC 下的 熔断、降级、监控、Opentracing 等等功能
+* 在分布式下,Jboot 支持了分布式缓存、分布式 Session、分布式锁、分布式任务、统一配置中心
+* 在数据库下,Jboot 支持分库分表、支持 Reids 等 nosql 数据库的极简调用
+* 在 MQ 下,Jboot 支持 rabbitmq、redismq、rocketmq 甚至还支持了 阿里云的商业MQ
+* 另外,Jboot 还支持了 Swagger、Event 事件机制、高并发下的 Sentinel 限流方案等等更多的惊喜
+
+
+希望你用的顺手、开心,如果有什么问题,可以通过 顶部菜单的 提问 链接进行提问,我回在第一时间回复您。
diff --git a/doc/docs/static/images/0008.png b/doc/docs/static/images/0008.png
new file mode 100644
index 0000000000000000000000000000000000000000..da156921beb4cc2082eb7449343e59d13b02fb91
Binary files /dev/null and b/doc/docs/static/images/0008.png differ
diff --git a/doc/docs/static/images/0009.png b/doc/docs/static/images/0009.png
new file mode 100644
index 0000000000000000000000000000000000000000..62b152099aa347dc1bd474855e0a419fd00a320f
Binary files /dev/null and b/doc/docs/static/images/0009.png differ
diff --git a/doc/docs/static/images/0010.png b/doc/docs/static/images/0010.png
new file mode 100644
index 0000000000000000000000000000000000000000..fd48654ada1c755a25f49ff154534bac9c6a0dfd
Binary files /dev/null and b/doc/docs/static/images/0010.png differ
diff --git a/doc/docs/static/images/0011.png b/doc/docs/static/images/0011.png
new file mode 100644
index 0000000000000000000000000000000000000000..248297e3dbe3bcdc4726ce06c8638d4b6b250ec6
Binary files /dev/null and b/doc/docs/static/images/0011.png differ
diff --git a/doc/docs/static/images/0012.png b/doc/docs/static/images/0012.png
new file mode 100644
index 0000000000000000000000000000000000000000..c2a983f68a9dc6eeb4426402db530a72969a99bf
Binary files /dev/null and b/doc/docs/static/images/0012.png differ
diff --git a/doc/docs/static/images/0013.png b/doc/docs/static/images/0013.png
new file mode 100644
index 0000000000000000000000000000000000000000..05371b914eee623fa7024d4abe18ec2e88d54165
Binary files /dev/null and b/doc/docs/static/images/0013.png differ
diff --git a/doc/docs/static/images/0014.png b/doc/docs/static/images/0014.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a3742f8de1e503c000c315910c461a11a570e6f
Binary files /dev/null and b/doc/docs/static/images/0014.png differ
diff --git a/doc/docs/static/images/0015.png b/doc/docs/static/images/0015.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d8c0b3be9c57224339edfb1d339ee79e2ecdfe4
Binary files /dev/null and b/doc/docs/static/images/0015.png differ
diff --git a/doc/docs/static/images/0016.png b/doc/docs/static/images/0016.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b1a43edba866ffe3802238631ebebd2aaadd1cc
Binary files /dev/null and b/doc/docs/static/images/0016.png differ
diff --git a/doc/docs/static/images/1_eclipse.png b/doc/docs/static/images/1_eclipse.png
new file mode 100755
index 0000000000000000000000000000000000000000..10b75a2810fdd99ed9b75cfc082ff10d599da530
Binary files /dev/null and b/doc/docs/static/images/1_eclipse.png differ
diff --git a/doc/docs/static/images/1_ieda.png b/doc/docs/static/images/1_ieda.png
new file mode 100755
index 0000000000000000000000000000000000000000..1e87b58a892477335f901308fda853df23ee6b9b
Binary files /dev/null and b/doc/docs/static/images/1_ieda.png differ
diff --git a/doc/docs/static/images/ahas.png b/doc/docs/static/images/ahas.png
new file mode 100644
index 0000000000000000000000000000000000000000..24bb1308a563278ef3be9f1fe14f53264d3444d7
Binary files /dev/null and b/doc/docs/static/images/ahas.png differ
diff --git a/doc/docs/static/images/apidoc.jpg b/doc/docs/static/images/apidoc.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b17c102e84b482bbd8009f523dfbaea9d6da541f
Binary files /dev/null and b/doc/docs/static/images/apidoc.jpg differ
diff --git a/doc/docs/static/images/apidoc1.jpg b/doc/docs/static/images/apidoc1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..db3350433a1c3003064c0bcc5a29ea304accf6b0
Binary files /dev/null and b/doc/docs/static/images/apidoc1.jpg differ
diff --git a/doc/docs/imgs/grafana.png b/doc/docs/static/images/grafana.png
similarity index 100%
rename from doc/docs/imgs/grafana.png
rename to doc/docs/static/images/grafana.png
diff --git a/doc/docs/static/images/grafana_datasource_new.png b/doc/docs/static/images/grafana_datasource_new.png
new file mode 100644
index 0000000000000000000000000000000000000000..124fbef87705b6bad41aa3f012b14a73905a6285
Binary files /dev/null and b/doc/docs/static/images/grafana_datasource_new.png differ
diff --git a/doc/docs/static/images/grafana_import.png b/doc/docs/static/images/grafana_import.png
new file mode 100644
index 0000000000000000000000000000000000000000..b716364f69e627dba92acb9ca2f365a05b40375d
Binary files /dev/null and b/doc/docs/static/images/grafana_import.png differ
diff --git a/doc/docs/static/images/grafana_import_json.png b/doc/docs/static/images/grafana_import_json.png
new file mode 100644
index 0000000000000000000000000000000000000000..91391fb0acc81ce946d23dec28efebc3ace9f838
Binary files /dev/null and b/doc/docs/static/images/grafana_import_json.png differ
diff --git a/doc/docs/static/images/grafana_jboot_jvm.png b/doc/docs/static/images/grafana_jboot_jvm.png
new file mode 100644
index 0000000000000000000000000000000000000000..dadc1e24763317f7c5bbe8c9904221f802e2c800
Binary files /dev/null and b/doc/docs/static/images/grafana_jboot_jvm.png differ
diff --git a/doc/docs/imgs/idea-auto-build.jpg b/doc/docs/static/images/idea-auto-build.jpg
similarity index 100%
rename from doc/docs/imgs/idea-auto-build.jpg
rename to doc/docs/static/images/idea-auto-build.jpg
diff --git a/doc/docs/static/images/idea_001.jpg b/doc/docs/static/images/idea_001.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2516433b99504cdd8779bdb2fca4cb4a79b6d09c
Binary files /dev/null and b/doc/docs/static/images/idea_001.jpg differ
diff --git a/doc/docs/static/images/idea_002.png b/doc/docs/static/images/idea_002.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c4ec19d6280e71cb4dd6fe90a3faf862351de03
Binary files /dev/null and b/doc/docs/static/images/idea_002.png differ
diff --git a/doc/docs/static/images/idea_003.png b/doc/docs/static/images/idea_003.png
new file mode 100644
index 0000000000000000000000000000000000000000..14809cd5b0274538aca622779eba1b9835225008
Binary files /dev/null and b/doc/docs/static/images/idea_003.png differ
diff --git a/doc/docs/static/images/idea_004.png b/doc/docs/static/images/idea_004.png
new file mode 100644
index 0000000000000000000000000000000000000000..1bb8fa47ab869d2f359554e0c95b51a790be7e74
Binary files /dev/null and b/doc/docs/static/images/idea_004.png differ
diff --git a/doc/docs/static/images/idea_005.png b/doc/docs/static/images/idea_005.png
new file mode 100644
index 0000000000000000000000000000000000000000..ad9ae1ee13e5535e1bf75905a3096fe7f4b108d3
Binary files /dev/null and b/doc/docs/static/images/idea_005.png differ
diff --git a/doc/docs/static/images/idea_006.png b/doc/docs/static/images/idea_006.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe50b6c16bb42aef8be483fbb6db3db19221f067
Binary files /dev/null and b/doc/docs/static/images/idea_006.png differ
diff --git a/doc/docs/static/images/idea_007.png b/doc/docs/static/images/idea_007.png
new file mode 100644
index 0000000000000000000000000000000000000000..a8756ad37b9a6067ed6963544d555abc2a2b3224
Binary files /dev/null and b/doc/docs/static/images/idea_007.png differ
diff --git a/doc/docs/static/images/jboot-logo.png b/doc/docs/static/images/jboot-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..67983802cd3a645e7425ef7528f420e1908c0ace
Binary files /dev/null and b/doc/docs/static/images/jboot-logo.png differ
diff --git a/doc/docs/static/images/jboot-wechat-group.png b/doc/docs/static/images/jboot-wechat-group.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f607d8a4a2c7a3697dd82cd176e53e15e7c9ce2
Binary files /dev/null and b/doc/docs/static/images/jboot-wechat-group.png differ
diff --git a/doc/docs/static/images/prometheus_targets.png b/doc/docs/static/images/prometheus_targets.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d673227ba0f4cd79105cf65fd188b7dab948980
Binary files /dev/null and b/doc/docs/static/images/prometheus_targets.png differ
diff --git a/doc/docs/undertow.md b/doc/docs/undertow.md
index f5e029e96dcb4be262cbce1018e53acf4b84c375..74eef406bebd7f5f1bfce7a608fdd67e9df016ad 100644
--- a/doc/docs/undertow.md
+++ b/doc/docs/undertow.md
@@ -11,7 +11,7 @@
## 其他扩展
### 扩展1:配置位置
-在 Jboot 应用中,除了可以在 resource 目录下的 `undertow.txt` 文件进行配置以外,也可以在 `jboot.properties` 文件里配置。
+在 Jboot 应用中,除了可以在 resource 目录下的 `undertow.txt` 文件进行配置以外,也可以在 `jboot.properties` 文件里配置。
同时可以通过启动参数 和 环境变量等进行配置,Undertow 启动的时候读取配置内容的优先顺序是:
diff --git a/doc/docs/upgrade.md b/doc/docs/upgrade.md
index 193d7dc3b63ff3e16295f0c915decea493187490..9b8b8e1177096a0589c819b0124d068e0374034c 100644
--- a/doc/docs/upgrade.md
+++ b/doc/docs/upgrade.md
@@ -21,6 +21,7 @@
JbootCache cache = Jboot.me().getCache();
```
修改为:
+
```java
JbootCache cache = Jboot.getCache();
```
diff --git a/doc/docs/validator.md b/doc/docs/validator.md
new file mode 100644
index 0000000000000000000000000000000000000000..2c2c13379774aab0410d01b1de38d4785880e63f
--- /dev/null
+++ b/doc/docs/validator.md
@@ -0,0 +1,184 @@
+# 数据验证 Validator
+
+Jboot 从 V3.7.5 开始,增强 Jboot 的验证方式,在 Jboot 之前的 @EmptyValidate、@RegexValidate 等基础上,进一步基于 JSR 303 – Bean Validation 简化了验证方式,相比 Spring 更加优雅简单。
+
+[[toc]]
+
+## @NotNull
+
+在 Controller (或 Service 等)中,我们可以直接通过 @NotNull 注解给 Controller 添加,例如:
+
+```java
+@RequestMapping("/")
+public class MyController extends JbootController {
+
+ public void test(@NotNull String para) {
+ renderText("test6");
+ }
+}
+```
+
+当我们访问 `/test` 的时候,会出现如下的错误:
+
+```
+para is null at method: io.jboot.test.MyController.test(java.lang.String) : /test
+io.jboot.components.valid.ValidException : 不能为null
+ at io.jboot.components.valid.ValidUtil.throwValidException(ValidUtil.java:59)
+ at io.jboot.components.valid.ValidUtil.throwValidException(ValidUtil.java:50)
+ at io.jboot.components.valid.interceptor.NotNullInterceptor.intercept(NotNullInterceptor.java:36)
+```
+
+如果是 ajax (或者 content-type 为 "application/json") 访问 `/test` 的时候,会返回如下的 json 信息:
+
+```json
+{
+ "throwable": "io.jboot.components.valid.ValidException: 不能为null",
+ "errorMessage": "para is null at method: io.jboot.test.MyController.test(java.lang.String)",
+ "errorCode": 400,
+ "state": "fail",
+ "message": "不能为null"
+}
+```
+
+如果我们访问 `/test?para=123`,则可以正常访问,不会出错,此时 `test` 方法里的 para 的值为 `123`(不为 null)。
+
+## @Size 验证
+
+@Size 验证,不仅仅可以验证 String 数据的长度,也可以验证 int long 等数据类型的值的大小范围。比如:
+
+```java
+public void test(@Size(min=2,max=10) int value) {
+ renderText("test6");
+}
+```
+
+这个要求 value 的值必须在 2 ~ 10 之间。
+
+
+当然,我们还可以使用 @Size 来验证 `Map`、`List`、`数组` 的长度,比如配合 @JsonBody 来接收前端传入的值:
+
+```java
+public void list(@Size(min=2,max=10) @JsonBody() List list) {
+ System.out.println("list--->" + list);
+ renderText("ok");
+}
+```
+
+要求前度传入的 MyBean Json 数组的数量必须是在 2~10 之间。
+
+> 更多关于 @JsonBody 请参考:[这个链接](./json.md)。
+
+## @NotEmpty 验证
+
+@NotEmpty 不仅仅可以验证 String 类型不能为 null 和 空字符串,也可以验证 Map、List、数组等不能为空,比如:
+
+```java
+public void list(@NotEmpty() @JsonBody() List list) {
+ System.out.println("list--->" + list);
+ renderText("ok");
+}
+```
+
+要求前端掺入的 MyBean Json 数组必须有值。
+
+## @Valid 验证
+@Valid 是针对整个 Java Bean 验证,也可以对 JFinal 的 Model 进行验证。的 MyBean Json 数
+
+比如 MyBean 定义如下:
+
+```java
+public class MyBean {
+ private String id;
+
+ @NotBlank(message = "密码不能为空")
+ private String password;
+
+ @Size(min=0,max=2,message = "性别的值只能是 0 1 2")
+ private int sex;
+
+ @Min(value = 18,message = "未成年禁止入内")
+ private Integer age;
+}
+```
+
+在 Controller 或者 Service 中,如下代码可以直接对 MyBean 进行验证:
+
+```java
+public void test(@Valid() MyBean bean) {
+ renderText("test6");
+}
+```
+
+如果 MyBean 是一个 JFinal 的 Model,我们只需要在 getter 方法添加注解即可。
+
+## 更多的验证
+除了以上的基本示例以外,Jboot 的验证还支持了更多的验证:
+
+| 注解 | 说明 |
+| ---- | ---- |
+| @NotNull | 限制必须不为null |
+| @DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
+| @DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
+| @Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
+| @Max(value) | 限制必须为一个不大于指定值的数字 |
+| @Min(value) | 限制必须为一个不小于指定值的数字 |
+| @Pattern(value) | 限制必须符合指定的正则表达式 |
+| @Size(max,min) | 限制字符长度必须在min到max之间 |
+| @NotEmpty | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
+| @NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
+| @Email | 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
+
+## @EmptyValidate 和 @RegexValidate
+
+当如果我们的 `Controller` 的方法没有参数的时候,就无法使用如上的注解进行验证了。因为,以上的注解
+是给方法里的参数进行添加。在这个时候,我们可以使用 `@EmptyValidate` 和 `@RegexValidate` 进行验证。
+
+比如:
+
+```java
+@RequestMapping("/")
+public class MyController extends JbootController {
+
+ @EmptyValidate(@Form(name = "mobile", message = "手机号不能为空"))
+ public void action1() {
+ renderText("ok");
+ }
+
+
+ @RegexValidate(@RegexForm(name = "mobile", regex = Regex.MOBILE, message= "您输入的不是手机号"))
+ public void action2() {
+ renderText("ok");
+ }
+
+}
+```
+
+- 当我们访问 `/action1` 的时候,会出现手机号不能为空的错误提示。访问 `/action1?mobile=123` 的时候,正常访问。
+- 当我们访问 `/action2` 或者 `/action2?mobile=123` 的时候,会出现手机号不正确的错误提示。
+ 访问 `/action2?mobile=18611223344` 的时候,正常访问。因为 `18611223344` 是一个正确的手机号 。
+
+## 自定义渲染器
+
+ 我们可以写一个 `RenderFactory` 继承自 `JbootRenderFactory` ,并复写其 `getValidErrorRender()` 方法。
+
+ 例如:
+
+```java
+public MyRenderFactory extends JbootRenderFactory(){
+
+ @Override
+ public ValidErrorRender getValidErrorRender(ValidException validException){
+ //return 返回自己写渲染器
+ }
+}
+```
+
+然后在项目启动的时候,通过在 `onConstantConfig(Constants constants)` 里配置上自己的 `MyRenderFactory`:
+
+```java
+@Override
+public void onConstantConfig(Constants constants) {
+ constants.setRenderFactory(new MyRenderFactory());
+}
+
+```
\ No newline at end of file
diff --git a/doc/jboot_jvm_grafana.json b/doc/jboot_jvm_grafana.json
new file mode 100644
index 0000000000000000000000000000000000000000..f62c6e673276b85ad5b03d296c1a4db83e93ea66
--- /dev/null
+++ b/doc/jboot_jvm_grafana.json
@@ -0,0 +1,3628 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "limit": 100,
+ "name": "Annotations & Alerts",
+ "showIn": 0,
+ "type": "dashboard"
+ },
+ {
+ "datasource": "Prometheus",
+ "enable": true,
+ "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0",
+ "iconColor": "rgba(255, 96, 96, 1)",
+ "name": "Restart Detection",
+ "showIn": 0,
+ "step": "1m",
+ "tagKeys": "restart-tag",
+ "textFormat": "uptime reset",
+ "titleFormat": "Restart"
+ }
+ ]
+ },
+ "description": "Jboot 应用的 JVM 大盘",
+ "editable": true,
+ "gnetId": 4701,
+ "graphTooltip": 1,
+ "id": 4,
+ "iteration": 1610259558892,
+ "links": [],
+ "panels": [
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 125,
+ "panels": [],
+ "repeat": null,
+ "title": "Quick Facts",
+ "type": "row"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": true,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "datasource": "Prometheus",
+ "decimals": 1,
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "format": "s",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 1
+ },
+ "height": "",
+ "id": 63,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "70%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "jvm_uptime{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "metric": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "thresholds": "",
+ "title": "Uptime",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": true,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "datasource": "Prometheus",
+ "decimals": null,
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "format": "dateTimeAsIso",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 1
+ },
+ "height": "",
+ "id": 92,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "70%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "jvm_start_time{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "metric": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "thresholds": "",
+ "title": "Start time",
+ "type": "singlestat",
+ "valueFontSize": "70%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": true,
+ "colors": [
+ "rgba(50, 172, 45, 0.97)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(245, 54, 54, 0.9)"
+ ],
+ "datasource": "Prometheus",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "format": "percent",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 1
+ },
+ "id": 65,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "70%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_heap_used{application=\"$application\"})*100/sum(jvm_memory_heap_max{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "thresholds": "70,90",
+ "title": "Heap used",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": true,
+ "colors": [
+ "rgba(50, 172, 45, 0.97)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(245, 54, 54, 0.9)"
+ ],
+ "datasource": "Prometheus",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "format": "percent",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 1
+ },
+ "id": 75,
+ "interval": null,
+ "links": [],
+ "mappingType": 2,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "70%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ },
+ {
+ "from": "-99999999999999999999999999999999",
+ "text": "N/A",
+ "to": "0"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_non_heap_used{application=\"$application\"})*100/sum(jvm_memory_non_heap_max{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "",
+ "refId": "A",
+ "step": 14400
+ }
+ ],
+ "thresholds": "70,90",
+ "title": "Non-Heap used",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ },
+ {
+ "op": "=",
+ "text": "x",
+ "value": ""
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 4
+ },
+ "id": 126,
+ "panels": [],
+ "repeat": null,
+ "title": "I/O Overview",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 111,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(jboot_http_requests_count{application=\"$application\"}[1m]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Rate",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "ops",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {
+ "HTTP": "#890f02",
+ "HTTP - 5xx": "#bf1b00"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 112,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(jboot_http_responseCodes_serverError_total{application=\"$application\"}[1m]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - 5xx",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Errors",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "ops",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {
+ "HTTP": "#890f02",
+ "HTTP - 5xx": "#bf1b00"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 135,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(jboot_http_responseCodes_notFound_total{application=\"$application\"}[1m]))",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - 5xx",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "404 Not Fund",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "ops",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 5
+ },
+ "hiddenSeries": false,
+ "id": 113,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(jboot_http_requests_sum{application=\"$application\"}[1m]))/sum(rate(jboot_http_requests_count{application=\"$application\"}[1m]))",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - AVG",
+ "refId": "A"
+ },
+ {
+ "expr": "max(jboot_http_requests_max{application=\"$application\"})",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "HTTP - MAX",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Duration",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ns",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 12
+ },
+ "id": 127,
+ "panels": [],
+ "repeat": null,
+ "title": "JVM Memory",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 24,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_heap_used{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_heap_committed{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_heap_max{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "JVM Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 25,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_non_heap_used{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_non_heap_committed{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_non_heap_max{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "JVM Non-Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 26,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_total_used{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_total_committed{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "committed",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "expr": "sum(jvm_memory_total_max{application=\"$application\"})",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "refId": "C",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "JVM Total",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 13
+ },
+ "hiddenSeries": false,
+ "id": 86,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": true,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "vss",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": " {application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "rss",
+ "refId": "B"
+ },
+ {
+ "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "swap",
+ "refId": "C"
+ },
+ {
+ "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "total",
+ "refId": "D"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "JVM Process Memory",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 20
+ },
+ "id": 128,
+ "panels": [],
+ "repeat": null,
+ "title": "JVM Misc",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 106,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "system_cpu_usage{application=\"$application\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "system",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "process_cpu_usage{application=\"$application\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "process",
+ "refId": "B"
+ },
+ {
+ "expr": "avg_over_time(process_cpu_usage{application=\"$application\"}[1h])",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "process-1h",
+ "refId": "C"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "CPU Usage",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 1,
+ "format": "percentunit",
+ "label": "",
+ "logBase": 1,
+ "max": "1",
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 93,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "system_load_average_1m{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "system-1m",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "system_cpu_count{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "cpus",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Load",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 1,
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 32,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_threads_waiting_count{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "waiting",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "jvm_threads_daemon_count{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "daemon",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ },
+ {
+ "expr": "jvm_threads_blocked_count{application=\"$applicstance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "blocked",
+ "refId": "C",
+ "step": 2400
+ },
+ {
+ "expr": "jvm_threads_total_started_count{application=\"$applicaion\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "total",
+ "refId": "D",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Threads",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {
+ "blocked": "#bf1b00",
+ "new": "#fce2de",
+ "runnable": "#7eb26d",
+ "terminated": "#511749",
+ "timed-waiting": "#c15c17",
+ "waiting": "#eab839"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 21
+ },
+ "hiddenSeries": false,
+ "id": 124,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_threads_count{application=\"$application\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "{{state}}",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Thread States",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 28
+ },
+ "hiddenSeries": false,
+ "id": 61,
+ "legend": {
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "process_files_open{application=\"$application\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "open",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "process_files_max{application=\"$application\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "File Descriptors",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "label": null,
+ "logBase": 10,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 35
+ },
+ "id": 129,
+ "panels": [],
+ "repeat": "persistence_counts",
+ "title": "JVM Memory Pools",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 9,
+ "x": 0,
+ "y": 36
+ },
+ "hiddenSeries": false,
+ "id": 3,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "maxPerRow": 3,
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "repeat": "jvm_memory_pool_heap",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_memory_heap_used{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 1800
+ },
+ {
+ "expr": "jvm_memory_heap_committed{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "commited",
+ "metric": "",
+ "refId": "B",
+ "step": 1800
+ },
+ {
+ "expr": "jvm_memory_heap_max{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "C",
+ "step": 1800
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 9,
+ "x": 9,
+ "y": 36
+ },
+ "hiddenSeries": false,
+ "id": 78,
+ "legend": {
+ "alignAsTable": false,
+ "avg": false,
+ "current": true,
+ "max": true,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "maxPerRow": 3,
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "repeat": "jvm_memory_pool_nonheap",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_memory_non_heap_used{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 1800
+ },
+ {
+ "expr": "jvm_memory_non_heap_committed{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "commited",
+ "metric": "",
+ "refId": "B",
+ "step": 1800
+ },
+ {
+ "expr": "jvm_memory_non_heap_max{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "max",
+ "metric": "",
+ "refId": "C",
+ "step": 1800
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Non Heap",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "mbytes",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 43
+ },
+ "id": 131,
+ "panels": [],
+ "repeat": null,
+ "title": "Garbage Collection",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 44
+ },
+ "hiddenSeries": false,
+ "id": 98,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "rate(jvm_gc_pause_count{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{action}} ({{cause}})",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Collections",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "ops",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 8,
+ "y": 44
+ },
+ "hiddenSeries": false,
+ "id": 101,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "rate(jvm_gc_pause_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_count{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "avg {{action}} ({{cause}})",
+ "refId": "A"
+ },
+ {
+ "expr": "jvm_gc_pause_max{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "max {{action}} ({{cause}})",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Pause Durations",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "s",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 16,
+ "y": 44
+ },
+ "hiddenSeries": false,
+ "id": 99,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "rate(jvm_gc_memory_allocated{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "allocated",
+ "refId": "A"
+ },
+ {
+ "expr": "rate(jvm_gc_memory_promoted{application=\"$application\", instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "promoted",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Allocated/Promoted",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "Bps",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 51
+ },
+ "id": 132,
+ "panels": [],
+ "repeat": null,
+ "title": "Classloading",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 0,
+ "y": 52
+ },
+ "hiddenSeries": false,
+ "id": 37,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_classes_loaded{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "loaded",
+ "metric": "",
+ "refId": "A",
+ "step": 1200
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Classes loaded",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 12,
+ "y": 52
+ },
+ "hiddenSeries": false,
+ "id": 38,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "delta(jvm_classes_loaded{application=\"$application\",instance=\"$instance\"}[1m])",
+ "format": "time_series",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "delta-1m",
+ "metric": "",
+ "refId": "A",
+ "step": 1200
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Class delta",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "ops",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 59
+ },
+ "id": 133,
+ "panels": [],
+ "repeat": null,
+ "title": "Buffer Pools",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 0,
+ "y": 60
+ },
+ "hiddenSeries": false,
+ "id": 33,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_buffers_direct_used{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "jvm_buffers_direct_capacity{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "capacity",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Direct Buffers",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 6,
+ "y": 60
+ },
+ "hiddenSeries": false,
+ "id": 83,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_buffers_direct_count{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "count",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Direct Buffers",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 12,
+ "y": 60
+ },
+ "hiddenSeries": false,
+ "id": 85,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_buffers_mapped_used{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "used",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ },
+ {
+ "expr": "jvm_buffers_mapped_capacity{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "capacity",
+ "metric": "",
+ "refId": "B",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Mapped Buffers",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Prometheus",
+ "editable": true,
+ "error": false,
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "fill": 1,
+ "fillGradient": 0,
+ "grid": {
+ "leftLogBase": 1,
+ "leftMax": null,
+ "leftMin": null,
+ "rightLogBase": 1,
+ "rightMax": null,
+ "rightMin": null
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 6,
+ "x": 18,
+ "y": 60
+ },
+ "hiddenSeries": false,
+ "id": 84,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "alertThreshold": true
+ },
+ "percentage": false,
+ "pluginVersion": "7.3.1",
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "jvm_buffers_mapped_count{application=\"$application\", instance=\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 2,
+ "legendFormat": "count",
+ "metric": "",
+ "refId": "A",
+ "step": 2400
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Mapped Buffers",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "x-axis": true,
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "y-axis": true,
+ "y_formats": [
+ "short",
+ "short"
+ ],
+ "yaxes": [
+ {
+ "decimals": 0,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": 0,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ }
+ ],
+ "refresh": "30s",
+ "schemaVersion": 26,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "allValue": null,
+ "current": {
+ "selected": false,
+ "text": "jboot",
+ "value": "jboot"
+ },
+ "datasource": "Prometheus",
+ "definition": "",
+ "error": null,
+ "hide": 0,
+ "includeAll": false,
+ "label": "应用",
+ "multi": false,
+ "name": "application",
+ "options": [],
+ "query": "label_values(application)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "allValue": null,
+ "current": {
+ "selected": false,
+ "text": "localhost:1234",
+ "value": "localhost:1234"
+ },
+ "datasource": "Prometheus",
+ "definition": "label_values(instance)",
+ "error": null,
+ "hide": 0,
+ "includeAll": false,
+ "label": "实例",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "instance",
+ "options": [],
+ "query": "label_values(instance)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "allValue": null,
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": "Prometheus",
+ "definition": "",
+ "error": null,
+ "hide": 0,
+ "includeAll": true,
+ "label": "JVM Memory Pools Heap",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "jvm_memory_pool_heap",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allFormat": "glob",
+ "allValue": null,
+ "current": {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": "Prometheus",
+ "definition": "",
+ "error": null,
+ "hide": 0,
+ "includeAll": true,
+ "label": "JVM Memory Pools Non-Heap",
+ "multi": false,
+ "multiFormat": "glob",
+ "name": "jvm_memory_pool_nonheap",
+ "options": [],
+ "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 2,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-24h",
+ "to": "now"
+ },
+ "timepicker": {
+ "now": true,
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "Jboot JVM",
+ "uid": "F7CDMa-Mz",
+ "version": 1
+}
\ No newline at end of file
diff --git a/doc/jbootadmin/attachment.md b/doc/jbootadmin/attachment.md
new file mode 100644
index 0000000000000000000000000000000000000000..d6fa94f12ce7fc4da8f3bd5467059183c6c567dd
--- /dev/null
+++ b/doc/jbootadmin/attachment.md
@@ -0,0 +1,3 @@
+# 分布式文件同步
+
+购买后获得阅读权限。
\ No newline at end of file
diff --git a/doc/jbootadmin/buy.md b/doc/jbootadmin/buy.md
new file mode 100644
index 0000000000000000000000000000000000000000..edc01cc2f51adc256cf84d8cb27ad50899a2ae84
--- /dev/null
+++ b/doc/jbootadmin/buy.md
@@ -0,0 +1,3 @@
+# 购买 JbootAdmin
+
+购买 JbootAdmin 请联系海哥微信:wx198819880
\ No newline at end of file
diff --git a/doc/jbootadmin/cdn.md b/doc/jbootadmin/cdn.md
new file mode 100644
index 0000000000000000000000000000000000000000..32f5615c7f9360739a2ac59a00cdb6b5e5f2b836
--- /dev/null
+++ b/doc/jbootadmin/cdn.md
@@ -0,0 +1,8 @@
+# CDN 配置
+
+CDN 配置,我们只需要在 `jboot.properties` 里,添加如下代码。
+
+```
+jboot.web.cdn.enable = true
+jboot.web.cdn.domain = http://your-cdn-daomain.com
+```
\ No newline at end of file
diff --git a/doc/jbootadmin/config.md b/doc/jbootadmin/config.md
new file mode 100644
index 0000000000000000000000000000000000000000..bf00d2bd20e9f146724a920dd4b348516f91c91f
--- /dev/null
+++ b/doc/jbootadmin/config.md
@@ -0,0 +1,3 @@
+# 分布式配置中心
+
+购买后获得阅读权限。
\ No newline at end of file
diff --git a/doc/jbootadmin/db.md b/doc/jbootadmin/db.md
new file mode 100644
index 0000000000000000000000000000000000000000..13c6de98683fe32e5538f581f452efdca61af2b7
--- /dev/null
+++ b/doc/jbootadmin/db.md
@@ -0,0 +1,513 @@
+# JbootAdmin 数据库设计
+
+JbootAdmin 目前的表结构如下:
+
+
+**账户信息表** 用来存储每个账户的基本信息、账号密码
+```sql
+CREATE TABLE `account` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL COMMENT '租户用户ID,租户ID是自己的时候,自己是租户的管理员',
+ `superior_tenant_id` bigint(20) unsigned DEFAULT NULL COMMENT '上级租户ID',
+ `loginname` varchar(128) DEFAULT NULL COMMENT '登录名',
+ `nickname` varchar(128) DEFAULT NULL COMMENT '昵称',
+ `password` varchar(128) DEFAULT NULL COMMENT '密码',
+ `salt` varchar(32) DEFAULT NULL COMMENT '盐',
+ `email` varchar(64) DEFAULT NULL COMMENT '邮件',
+ `mobile` varchar(32) DEFAULT NULL COMMENT '手机电话',
+ `domain` varchar(32) DEFAULT NULL COMMENT 'SaaS系统给用户分配的域名',
+ `avatar` varchar(256) DEFAULT NULL COMMENT '账户头像',
+ `type` tinyint(2) DEFAULT NULL COMMENT '账户类型',
+ `status` tinyint(2) DEFAULT NULL COMMENT '状态',
+ `created` datetime DEFAULT NULL COMMENT '创建日期',
+ `activated` datetime DEFAULT NULL COMMENT '激活时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `loginname` (`loginname`) USING BTREE,
+ UNIQUE KEY `email` (`email`) USING BTREE,
+ UNIQUE KEY `mobile` (`mobile`) USING BTREE,
+ KEY `created` (`created`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE,
+ KEY `superior_tenant_id` (`superior_tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='账号信息表,保存用户账号信息。';
+```
+
+**账户地址** 用户的收货地址表
+```sql
+CREATE TABLE `account_address` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户 id',
+ `username` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '姓名',
+ `mobile` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
+ `province` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '省',
+ `city` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '市',
+ `district` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '区(县)',
+ `detail` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '详细地址到门牌号',
+ `zipcode` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮政编码',
+ `default_enable` tinyint(1) DEFAULT '0' COMMENT '是否默认,1是,0否',
+ `options` text COLLATE utf8mb4_unicode_ci COMMENT '扩展字段',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ `created` datetime DEFAULT NULL COMMENT '新增时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `account_id` (`account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='收货地址';
+```
+
+**用户属性表** 用来保持用户的扩展属性
+```sql
+CREATE TABLE `account_attr` (
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户ID',
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '属性名称',
+ `content` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '属性内容',
+ PRIMARY KEY (`account_id`,`name`) USING BTREE,
+ KEY `content` (`content`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='账户属性表';
+```
+
+**账户部门关系表** 账户和部门的 多对多 映射关系
+```sql
+CREATE TABLE `account_dept` (
+ `account_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '账户ID',
+ `dept_id` bigint(20) unsigned NOT NULL COMMENT '部门ID',
+ PRIMARY KEY (`dept_id`,`account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='部门和账户的多对多关系表';
+```
+
+**账户第三方关系表**
+```sql
+CREATE TABLE `account_openid` (
+ `account_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
+ `type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '第三方类型:wechat,dingding,qq...',
+ `openid` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '第三方的openId的值',
+ `access_token` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '可能用不到',
+ `expired_time` datetime DEFAULT NULL COMMENT 'access_token的过期时间',
+ `nickname` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
+ `avatar` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',
+ `options` text COLLATE utf8mb4_unicode_ci COMMENT '其他扩展属性',
+ `created` datetime DEFAULT NULL COMMENT '创建日期',
+ `modified` datetime DEFAULT NULL COMMENT '修改日期',
+ PRIMARY KEY (`type`,`account_id`) USING BTREE,
+ KEY `type_openid` (`type`,`openid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='账号第三方账号绑定表';
+```
+
+```sql
+CREATE TABLE `account_option` (
+ `account_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '账户ID',
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '扩展属性名称',
+ `content` text COLLATE utf8mb4_unicode_ci COMMENT '扩展属性内容',
+ PRIMARY KEY (`account_id`,`name`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='账户配置';
+```
+
+```sql
+CREATE TABLE `account_permission` (
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户ID',
+ `permission_id` varchar(128) NOT NULL DEFAULT '' COMMENT '权限ID',
+ `own` tinyint(1) DEFAULT NULL COMMENT '1 拥有, 0 排除',
+ PRIMARY KEY (`account_id`,`permission_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='账户和权限的多对多映射表,这个很少用到,只有在极特殊情况下,可以通过这个对某些权限进行添加或者排除';
+```
+
+```sql
+CREATE TABLE `account_receive_msg` (
+ `account_id` bigint(20) unsigned DEFAULT NULL COMMENT '账户id',
+ `send_msg_type` tinyint(2) unsigned DEFAULT NULL COMMENT '可接收消息类型',
+ KEY `account_id` (`account_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知功能';
+```
+
+```sql
+CREATE TABLE `account_role` (
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户ID',
+ `role_id` bigint(20) unsigned NOT NULL COMMENT '权限ID',
+ PRIMARY KEY (`account_id`,`role_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='账户和角色的多对多映射表';
+```
+
+```sql
+CREATE TABLE `account_scope` (
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户ID',
+ `scope_account_id` bigint(20) unsigned NOT NULL COMMENT '可以操作其他账户数据的账户ID',
+ PRIMARY KEY (`account_id`,`scope_account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='账户的操作范围(只可以操作哪些用户的数据)';
+```
+
+```sql
+CREATE TABLE `account_session` (
+ `id` varchar(32) NOT NULL DEFAULT '' COMMENT 'session id',
+ `client` varchar(32) NOT NULL DEFAULT '' COMMENT '客户端',
+ `platform` varchar(32) DEFAULT NULL COMMENT '登录平台',
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '账户id',
+ `expire_at` datetime NOT NULL COMMENT '到期时间',
+ `options` text COMMENT '扩展属性',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='账户登录session';
+```
+
+```sql
+CREATE TABLE `account_station` (
+ `account_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '账户ID',
+ `station_id` bigint(20) unsigned NOT NULL COMMENT '岗位ID',
+ PRIMARY KEY (`station_id`,`account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='岗位和账户的多对多关系';
+```
+
+```sql
+CREATE TABLE `dev_project` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目名称',
+ `module_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '模块名称',
+ `old_module_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '旧模块名称,用于在模块名称更新的时候,需要记录之前的模块名称',
+ `package_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '模块报名',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '模块描述',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='项目表,对应的是 idea(或者eclipse)的代码模块';
+```
+
+```sql
+CREATE TABLE `dev_tablefield` (
+ `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字段名称',
+ `table_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段所在的表名',
+ `title` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段的标题',
+ `render_type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渲染类型',
+ `valid_required` tinyint(1) DEFAULT NULL COMMENT '是否必填',
+ `valid_mobile` tinyint(1) DEFAULT NULL COMMENT '是否是手机',
+ `valid_email` tinyint(1) DEFAULT NULL COMMENT '是否是邮件',
+ `show_in_list` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否在列表里显示',
+ `show_in_edit` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否在编辑页面编辑',
+ `search_type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '搜索类型 like、相等',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `table_name` (`table_name`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='表字段配置';
+```
+
+```sql
+CREATE TABLE `dev_tableinfo` (
+ `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '表名',
+ `project_id` int(11) DEFAULT NULL COMMENT '所在项目',
+ `controller_mapping` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Controller 的 url 映射',
+ `title` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '标题',
+ `alias` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '别名',
+ `help_text` text COLLATE utf8mb4_unicode_ci COMMENT '帮助内容',
+ `crumbs_text` text COLLATE utf8mb4_unicode_ci COMMENT '面包屑内容',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '描述或备注',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`name`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='表生成内容配置';
+```
+
+```sql
+CREATE TABLE `log_action` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint(20) unsigned DEFAULT NULL COMMENT '用户ID',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL,
+ `action` varchar(512) DEFAULT NULL COMMENT '访问路径',
+ `name` varchar(128) DEFAULT NULL,
+ `query` varchar(512) DEFAULT NULL COMMENT '访问参数',
+ `ip` varchar(64) DEFAULT NULL COMMENT 'IP',
+ `ua` varchar(1024) DEFAULT NULL COMMENT '浏览器',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `account_id` (`account_id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户行为日志';
+```
+
+```sql
+CREATE TABLE `log_login` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint(20) unsigned NOT NULL COMMENT '登录账户',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL,
+ `client` varchar(100) DEFAULT NULL COMMENT '登录客户端',
+ `ip` varchar(100) DEFAULT NULL COMMENT '登录的I IP 地址',
+ `ua` varchar(512) DEFAULT NULL COMMENT '登录的浏览器 ua',
+ `platform` varchar(32) DEFAULT NULL COMMENT '登录平台',
+ `created` datetime NOT NULL COMMENT '登录时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `account_id` (`account_id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户登录日志';
+```
+
+```sql
+CREATE TABLE `sys_cnarea` (
+ `id` mediumint(7) unsigned NOT NULL AUTO_INCREMENT,
+ `level` tinyint(2) unsigned NOT NULL COMMENT '层级',
+ `parent_code` bigint(14) unsigned NOT NULL DEFAULT '0' COMMENT '父级行政代码',
+ `area_code` bigint(14) unsigned NOT NULL DEFAULT '0' COMMENT '行政代码',
+ `zip_code` mediumint(6) unsigned zerofill NOT NULL DEFAULT '000000' COMMENT '邮政编码',
+ `city_code` char(6) NOT NULL DEFAULT '' COMMENT '区号',
+ `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
+ `short_name` varchar(50) NOT NULL DEFAULT '' COMMENT '简称',
+ `merger_name` varchar(50) NOT NULL DEFAULT '' COMMENT '组合名',
+ `pinyin` varchar(30) NOT NULL DEFAULT '' COMMENT '拼音',
+ `lng` decimal(10,6) NOT NULL DEFAULT '0.000000' COMMENT '经度',
+ `lat` decimal(10,6) NOT NULL DEFAULT '0.000000' COMMENT '纬度',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `uk_code` (`area_code`) USING BTREE,
+ KEY `idx_parent_code` (`parent_code`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='中国行政地区表';
+```
+
+```sql
+CREATE TABLE `sys_dept` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '部门ID',
+ `type` tinyint(2) DEFAULT NULL COMMENT '部门类型',
+ `pid` bigint(20) unsigned DEFAULT NULL COMMENT '上级部门id',
+ `code` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门编码',
+ `name` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门名称',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '部门描述',
+ `sort_no` int(11) DEFAULT NULL COMMENT '排序编号',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL COMMENT '租户ID',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='部门表';
+```
+
+```sql
+CREATE TABLE `sys_dept_account` (
+ `dept_id` bigint(20) unsigned NOT NULL COMMENT '部门ID',
+ `account_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '账户ID',
+ PRIMARY KEY (`dept_id`,`account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='部门和账户的多对多关系表';
+```
+
+```sql
+CREATE TABLE `sys_menu` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `menu_id` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '菜单对应html的id属性的值',
+ `menu_pid` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '通过 PID 来设置上下级关系',
+ `text` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单内容',
+ `icon` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单ICON',
+ `url` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单URL地址',
+ `target` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单的打开类型',
+ `type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单类型',
+ `platform` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单所在的平台,一个系统下可能存在多个平台',
+ `sort_no` int(11) DEFAULT NULL COMMENT '排序值',
+ `options` text COLLATE utf8mb4_unicode_ci COMMENT '其他扩展字段',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `platform` (`platform`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='系统菜单';
+```
+
+```sql
+CREATE TABLE `sys_message` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `title` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '消息标题',
+ `level` tinyint(2) NOT NULL COMMENT '内容级别(1普通 2一般 3紧急 4弹窗)',
+ `type` tinyint(2) DEFAULT NULL COMMENT '内容类型(1系统 2业务 3公告 4其它)',
+ `content` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息内容',
+ `receive_type` tinyint(2) NOT NULL COMMENT '接受者类型(0全部 1用户 2部门 3角色 4岗位)',
+ `send_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '发送者用户编码',
+ `send_account_name` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发送者用户姓名',
+ `send_date` datetime DEFAULT NULL COMMENT '发送时间',
+ `notify_type` tinyint(2) DEFAULT NULL COMMENT '通知类型(PC APP 短信 邮件 微信)多选',
+ `status` tinyint(2) NOT NULL COMMENT '状态(0正常 1删除 4审核 5驳回 9草稿)',
+ `reply_status` tinyint(2) DEFAULT NULL COMMENT '是否支持回复等',
+ `reply_id` bigint(20) unsigned DEFAULT NULL COMMENT '回复的消息ID',
+ `created_by` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建者',
+ `created` datetime NOT NULL COMMENT '创建时间',
+ `modified_by` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '更新者',
+ `modified` datetime NOT NULL COMMENT '更新时间',
+ `remarks` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注信息',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `send_account_id` (`send_account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='系统消息';
+```
+
+```sql
+CREATE TABLE `sys_message_send_record` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `message_id` bigint(20) unsigned NOT NULL COMMENT '所属消息',
+ `send_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '发送消息用户',
+ `receive_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '接受者用户',
+ `read_status` tinyint(2) NOT NULL COMMENT '读取状态(0未送达 1已读 2未读)',
+ `read_date` datetime DEFAULT NULL COMMENT '阅读时间',
+ `reply_status` tinyint(2) DEFAULT NULL COMMENT '回复状态',
+ `reply_date` datetime DEFAULT NULL COMMENT '回复时间',
+ `is_star` tinyint(1) DEFAULT NULL COMMENT '是否标星',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `send_account_id` (`send_account_id`) USING BTREE,
+ KEY `receive_account_id` (`receive_account_id`) USING BTREE,
+ KEY `read_status` (`read_status`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='消息发送记录表';
+```
+
+```sql
+CREATE TABLE `sys_option` (
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '配置key',
+ `content` text COLLATE utf8mb4_unicode_ci COMMENT '配置内容',
+ PRIMARY KEY (`name`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='系统配置表';
+```
+
+```sql
+CREATE TABLE `sys_permission` (
+ `id` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限ID',
+ `group_id` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '所属的权限组ID',
+ `name` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限名称',
+ `description` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限描述',
+ `type` tinyint(2) DEFAULT NULL COMMENT '权限类型,1 菜单,2 Action,3 逻辑权限, 4 敏感数据权限, 5 页面元素,6 其他类型',
+ `platform` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `sort_no` int(11) DEFAULT NULL COMMENT '权限排序,只用于展示',
+ `created` datetime DEFAULT NULL COMMENT '创建修改',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `group_id` (`group_id`) USING BTREE,
+ KEY `platform` (`platform`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='权限表';
+```
+
+```sql
+CREATE TABLE `sys_permission_group` (
+ `id` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限组ID',
+ `name` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限组名称',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '权限组描述',
+ `sort_no` int(11) DEFAULT NULL COMMENT '权限组排序',
+ `platform` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限组所在的平台',
+ `type` tinyint(2) DEFAULT NULL COMMENT '权限组类型,1 菜单,2 Action,3 逻辑权限, 4 敏感数据权限, 5 页面元素,6 其他类型',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `platform` (`platform`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='对权限进行分组,只用于显示的作用,不起逻辑控制作用';
+```
+
+```sql
+CREATE TABLE `sys_role` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色ID',
+ `pid` bigint(20) unsigned DEFAULT NULL COMMENT '父级 id',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL COMMENT '住户 ID',
+ `name` varchar(128) NOT NULL DEFAULT '' COMMENT '角色名称',
+ `desc` text COMMENT '角色的描述',
+ `flag` varchar(64) DEFAULT '' COMMENT '角色标识,全局唯一,sa 为超级管理员',
+ `sort_no` int(11) DEFAULT NULL COMMENT '排序编码',
+ `created` datetime NOT NULL COMMENT '创建时间',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='角色表';
+```
+
+```sql
+CREATE TABLE `sys_role_permission` (
+ `role_id` bigint(20) unsigned NOT NULL COMMENT '角色ID',
+ `permission_id` varchar(128) NOT NULL DEFAULT '' COMMENT '权限ID',
+ PRIMARY KEY (`role_id`,`permission_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='角色和权限的多对多映射表';
+```
+
+```sql
+CREATE TABLE `sys_station` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '岗位名称',
+ `type` tinyint(2) DEFAULT NULL COMMENT '岗位分类(高管、中层、基层)',
+ `sort_no` int(10) DEFAULT NULL COMMENT '岗位排序(升序)',
+ `status` tinyint(2) DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+ `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ `modifiy_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
+ `modified` datetime DEFAULT NULL COMMENT '更新时间',
+ `remarks` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注信息',
+ `tenant_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '租户ID',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='岗位表';
+```
+
+```sql
+CREATE TABLE `wechat_account` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `name` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公众号名称',
+ `type` tinyint(4) DEFAULT NULL COMMENT '公众号类型',
+ `tenant_id` bigint(20) unsigned DEFAULT NULL COMMENT '租户ID',
+ `app_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公众号的 APP ID',
+ `app_secret` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公众号的 APP Secret',
+ `app_token` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公众号配置的 token',
+ `encoding_aes_key` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Aes 加密 key',
+ `message_encrypt` tinyint(1) DEFAULT NULL COMMENT '是否进行加密',
+ `description` text COLLATE utf8mb4_unicode_ci COMMENT '公众号描述',
+ `sort_no` int(11) DEFAULT NULL COMMENT '排序编号',
+ `status` tinyint(2) DEFAULT NULL COMMENT '状态',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `app_id` (`app_id`) USING BTREE,
+ KEY `tenant_id` (`tenant_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='微信公众号表';
+```
+
+```sql
+CREATE TABLE `wechat_account_keyword` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `wechat_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '归属公众号',
+ `wechat_account_appid` varchar(64) DEFAULT NULL,
+ `keyword` varchar(128) DEFAULT NULL COMMENT '关键字',
+ `reply_content_type` tinyint(2) DEFAULT NULL COMMENT '回复类型',
+ `reply_content` text COMMENT '回复内容,保存内容是一个 json',
+ `options` text,
+ `status` tinyint(2) DEFAULT NULL COMMENT '是否启用',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `ak` (`wechat_account_appid`,`keyword`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户自定义关键字回复表';
+```
+
+```sql
+CREATE TABLE `wechat_account_menu` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键 ID',
+ `pid` bigint(20) unsigned DEFAULT NULL COMMENT '父级ID',
+ `wechat_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '归属公众号',
+ `text` varchar(512) DEFAULT NULL COMMENT '文本内容',
+ `keyword` varchar(128) DEFAULT NULL COMMENT '关键字',
+ `type` varchar(32) DEFAULT '' COMMENT '菜单类型',
+ `sort_no` int(11) DEFAULT '0' COMMENT '排序字段',
+ `created` datetime DEFAULT NULL COMMENT '创建时间',
+ `modified` datetime DEFAULT NULL COMMENT '修改时间',
+ `status` tinyint(2) DEFAULT NULL COMMENT '状态',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `wechat_account_id` (`wechat_account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='微信公众号菜单表';
+```
+
+```sql
+CREATE TABLE `wechat_account_msg` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `wechat_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '微信公众号ID',
+ `wechat_account_appid` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信公众号的 AppID',
+ `user_open_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信消息发送用户',
+ `user_nickname` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发送消息的用户昵称',
+ `user_avatar` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发送消息的用户头像',
+ `content_type` tinyint(2) DEFAULT NULL COMMENT '消息类型',
+ `content` text COLLATE utf8mb4_unicode_ci COMMENT '消息内容',
+ `created` datetime DEFAULT NULL COMMENT '消息的接收时间',
+ `is_replied` tinyint(1) DEFAULT NULL COMMENT '是否已经回复',
+ `reply_date` datetime DEFAULT NULL COMMENT '回复时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `wechat_account_id` (`wechat_account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='微信公众号消息表';
+```
+
+```sql
+CREATE TABLE `wechat_account_msg_reply` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `wechat_account_id` bigint(20) unsigned DEFAULT NULL COMMENT '回复的微信公众号ID',
+ `reply_msg_id` bigint(20) unsigned DEFAULT NULL COMMENT '回复的消息ID',
+ `reply_to_open_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '回复的用户ID',
+ `reply_content_type` tinyint(2) DEFAULT NULL COMMENT '回复的消息类型',
+ `reply_content` text COLLATE utf8mb4_unicode_ci COMMENT '回复内容',
+ `created` datetime DEFAULT NULL COMMENT '回复时间',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `wechat_account_id` (`wechat_account_id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='微信公众号消息回复表';
+```
+
+```sql
+CREATE TABLE `wechat_account_option` (
+ `wechat_account_appid` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '微信公众号配置key',
+ `content` text COLLATE utf8mb4_unicode_ci COMMENT '微信公众号配置Neri',
+ PRIMARY KEY (`wechat_account_appid`,`name`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='微信公众号配置';
+
+```
\ No newline at end of file
diff --git a/doc/jbootadmin/deploy.md b/doc/jbootadmin/deploy.md
new file mode 100644
index 0000000000000000000000000000000000000000..bdc1024a6fbb5dbcf1a86a4847cff54a7b01ef63
--- /dev/null
+++ b/doc/jbootadmin/deploy.md
@@ -0,0 +1,115 @@
+# JbootAdmin 部署文档
+
+
+JbootAdmin 部署过程主要分为以下几个步骤:
+- 第一步:编译
+- 第二步:上传到服务器
+- 第三步:启动
+
+## 第一步:编译
+
+编译的目的是为了让我们的源码(包含 java、js、css 图片等)打包成一个可以被执行的 jar 文件,有了 jar 文件后,我们便可以通过 java 命令 java -jar 进行启动。
+
+在 JbootAdmin 中,我们需要通过 Maven 进行编译的,因此,在编译之前需要在您的电脑里,安装好 maven ,配置好 Maven 的环境变量。而 Maven 是使用 Java 开发的,因此在您的电脑安装好 Java 也是必须的。
+
+当安装好 Maven 后,我们需要通过命令窗口(shell) 方式进入项目所在根目录,然后执行如下代码:
+
+```
+mvn clean package
+```
+
+稍等片刻,会在 `starter-cms/target` 目录下,生成文件:starter-cms-1.0.0-jar-with-dependencies.jar。
+
+
+## 第二步:上传到服务器
+
+上传的工具有很多,比如 xshell 等,关于工具的使用请自行参考相关工具的文档,如果您的电脑是 linux 或者 mac 电脑,可以通过如下命令上传。
+
+```
+scp 本地文件目录 root@ip:/服务器目录
+```
+
+如下的命令是将本地文件 starter-cms-1.0.0-jar-with-dependencies.jar 上传到服务器 192.168.1.100 的 /data/wwww 目录里。
+
+```
+scp /your/path/to/starter-cms-1.0.0-jar-with-dependencies.jar root@192.168.1.100:/data/wwww
+```
+
+## 第三步:启动
+
+通过 ssh 登录到服务器后,进入到 `starter-cms-1.0.0-jar-with-dependencies.jar` 文件所在目录,执行命令
+
+java -jar starter-cms-1.0.0-jar-with-dependencies.jar 就可以启动项目,但是这种启动的方式是 "前台" 模式,当我们推出命令窗口后,项目也会自动停止了,因此,我们需要通过 nohup 将其后台启动。
+
+
+命令如下:
+
+```
+nohup java -jar ./starter-cms-1.0.0-jar-with-dependencies.jar >./log_cms.log 2>&1 &
+```
+
+同时项目的相关错误日志会输出到 ./log_cms.log 这个文件里。
+
+为了更加方便我们启动项目,我们也可以在 `starter-cms-1.0.0-jar-with-dependencies.jar` 目录下写一个 shell 脚本,用来启动或者关闭我们的项目,例如:
+
+```
+#!/bin/bash
+
+COMMAND="$1"
+
+if [[ "$COMMAND" != "start" ]] && [[ "$COMMAND" != "stop" ]] && [[ "$COMMAND" != "restart" ]]; then
+ echo "Usage: $0 start | stop | restart"
+ exit 0
+fi
+
+
+# 生成 class path 值
+
+function start()
+{
+ # 运行为后台进程,并且将信息输出到 log_cms.log 文件
+ nohup java -jar ./starter-cms-1.0.0-jar-with-dependencies.jar --jboot.app.mode=product >./log_cms.log 2>&1 &
+}
+
+function stop()
+{
+ kill `pgrep -f starter-cms` 2>/dev/null
+}
+
+if [[ "$COMMAND" == "start" ]]; then
+ start
+elif [[ "$COMMAND" == "stop" ]]; then
+ stop
+else
+ stop
+ start
+fi
+```
+
+假设这个文件名称为 `cms_deploy.sh` ,我们可以执行如下命令对项目进行控制
+
+- 启动项目 `./cms_deploy.sh start`
+- 停止项目 `./cms_deploy.sh stop`
+- 重启项目 `./cms_deploy.sh restart`
+
+
+## 注意事项:
+
+### 1、配置问题
+
+当我们把项目打包成一个 jar 文件后,所有的配置内容都存放在 jar 文件里的 jboot.porperites 里了,无法对其进行修改,但是如果一定要修改其配置,可以把这个配置添加到启动参数里,例如
+
+```
+java -jar ./starter-cms-1.0.0-jar-with-dependencies.jar --jboot.app.mode=product
+```
+
+等同于在 jboot.properties 文件里添加了 `jboot.app.mode=product` 的配置,而且会覆盖 jboot.properties 原本的配置。其他参数同理。
+
+另一个方案是在项目里有两个 properties 文件,分别是 jboot.properties 和 jboot-product.properties 文件,当我们启动应用的时候,如果当前的 app 模式是 product,那么系统将会优先读取
+`jboot-product.properties` 文件里的内容,只有读取不到的时候,才会去读取 `jboot.propertie` 的内容。更多关于配置问题,请参考:http://jbootprojects.gitee.io/docs/docs/config.html
+
+
+### 2、安全问题
+
+一般情况下,我们不会把数据库(或者 redis等)的明文密码直接配置在 `jboot.properties` 文件里,配置在 `jboot.properties` 里的密码一般都是加密的,关于配置内容加密的解决方案,请参考:
+http://jbootprojects.gitee.io/docs/docs/config.html#%E9%85%8D%E7%BD%AE%E5%86%85%E5%AE%B9%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86
diff --git a/doc/jbootadmin/feature.md b/doc/jbootadmin/feature.md
new file mode 100644
index 0000000000000000000000000000000000000000..c464e5df30ef73ee89b8c1f4ffe2188a30426c48
--- /dev/null
+++ b/doc/jbootadmin/feature.md
@@ -0,0 +1,324 @@
+# JbootAdmin 功能介绍
+
+[[TOC]]
+
+## 基础功能
+
+
+### 账户管理
+
+
+
+### 部门管理
+
+
+
+### 职位管理
+
+
+
+### 角色管理
+
+
+
+
+### 角色的权限分配
+对角色的权限进行分配、包括菜单的权限、功能的权限、逻辑权限(根据业务进行人为定义的权限)、敏感数据权限(根据业务进行人为定义的、涉及数据敏感的权限)
+
+
+
+### 参数配置
+
+
+
+### 行政区划
+
+
+
+### 数据字典
+JbootAdmin 中的数据字典不同于其他传统意义的数据字典。JbootAdmin 的数据字典可以生成枚举代码 `Enum` ,通过 `Enum` 又可以方便在在 Java 代码和模板中使用。
+
+比如,在后台创建的枚举,可以直接生成如下代码:
+
+```java
+@JFinalSharedEnum
+public enum PayType {
+
+ ALIPAY(1, "支付宝"),
+ WECHAT(10, "微信支付"),
+
+ public int value;
+ public String text;
+
+ PayType(int value, String text) {
+ this.value = value;
+ this.text = text;
+ }
+
+
+ public static String text(Integer value) {
+ if (value != null) {
+ for (AccountType type : values()) {
+ if (type.value == value) {
+ return type.text;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ public static boolean isAlipay(Integer value) {
+ return value != null && ALIPAY.value == value;
+ }
+
+}
+```
+
+而以上代码又可以方便在 Java 或者 模板中使用,例如:
+
+```html
+
+
+ #(PayType.ALIPAY.text)
+ #(PayType.text(1))
+ #(PayType.isAlipay(1))
+
+
+```
+
+
+
+
+
+### 微信公众号对接
+
+支持多个微信公众号,支持菜单配置、根据关键字自动回复、默认回复配置 等等
+
+
+
+自动回复
+
+
+通过关键字自动回复...
+
+
+
+
+
+
+## 特色功能(独创)
+
+### 特色功能1:同一套代码支持 Tab 模式和独立页面模式
+JbootAdmin 同一套代码,后台支持 Tab 模式,也支持独立页面模式,同时 Tab 模式和独立页面模式支持用户自主切换,也支持后台配置为固定,不允许用户切换。 如下图所示是 Tab 模式:
+
+
+
+### 特色功能2:免手动维护的权限列表
+在一般的系统中,需要我们一边开发,一边手动定义系统有哪些权限,但是在 JbootAdmin 中,所有的权限都是免手动维护的,我们可以通过后台,一键自动生成权限列表,存储到数据库里去。这样避免了繁杂的人为手动维护,也大大减少了出错的可能性。
+
+
+
+### 特色功能3:免手动维护的系统菜单
+
+原理同免维护的权限列表。后台一键构建左边菜单的功能。
+
+
+
+
+## API文档自动生成
+
+Jboot API 功能自动根据代码、自动生成文档、Debug 页面、对数据进行 Mock 等功能。
+
+### API 代码
+
+
+
+
+### API 生成文档
+
+
+
+### API Debug
+
+通过 Debug 功能,我们可以方便的对 API 进行调试。
+
+
+
+### API Mock
+
+通过 API Mock 功能,我们可以模拟 API 数据,在前后端分离的场景下,我们可以使用此功能先给前端团队正常调用数据,等我们完成 API 开发再删除 Mock 数据。
+
+
+
+## 强大代码生成功能
+
+在使用 JbootAdmin 的代码生成器之前,我们可以先创建项目,然后对该项目进行数据配置。
+
+### 项目列表
+
+
+### 创建项目,配置数据源
+
+
+### 根据数据表,生成代码
+
+
+### 根据某个表,生成 Model、Service、Provider、Controller、Html 等代码
+
+
+### 配置某个表对应的 Controller 映射等
+
+
+### 对表的字段进行配置等
+
+
+
+## 运维功能
+
+JbootAdmin 提供了强大的运维功能。1 是内置的功能,2 是通过适配第三方来进行配置。
+
+### 分布式应用列表
+
+可以查看分布式下的某个应用情况
+
+
+
+### 分布式监控大盘
+
+可以查看分布式下,每个应用所在的机器硬件情况。
+
+
+
+### 分布式缓存监控
+
+对分布式缓存情况查看,刷新等,支持了 ehcache、redis、caffeine、ehredis 等等。
+
+
+
+### Sentinel 分布式限流
+
+支持自建 Sentinel 控制,也支持阿里云的 AHAS 进行限流控制。
+
+
+
+### 基于 Nacos 门户网关自动发现
+
+
+
+
+
+### 基于 Grafana 对 Jboot 的 JVM 进行监控
+
+
+
+### 更多
+
+ 基于 Dubbo Admin 对 Jboot RPC 控制等不再截图...
+
+## Demos示例
+
+JbootAdmin 提供了一些 Demos 示例,方便用户对 JbootAdmin 内置的前端组件进行全面的了解。
+
+### 产品列表
+
+支持在产品列表里对产品进行基本的操作
+
+
+
+### 产品编辑
+
+在产品编辑中可以对产品的属性进行配置、支持多规格、多单位等等...
+
+
+
+### 产品库存入库单
+
+
+
+
+### 查看入库单
+
+
+
+
+### 编辑入库单
+
+
+
+
+
+
+
+## 课堂8 - 在线教育系统
+
+课堂8 是一个基于 JbootAdmin 开发的在线教育系统。
+
+### 课堂8 - 首页
+
+
+
+### 课堂8 - 课程详情
+
+在课程详情中,可以设置课程的标题、简介、营销简介、章节目录,是否免费试看、限时价(秒杀价)、会员价等等....
+
+
+
+### 课堂8 - 在线学习
+
+当用户未登录时,需要登录才能观看。
+
+
+用户登录后,可以正常观看视频,观看的过程中,会记录课程的当前进度,用户可以通过用户中心再次进入观看,继续学习。
+
+
+### 课堂8 - 用户微信注册或登录
+
+其流程是:扫码微信二维码 → 关注我们的公众号 → 自动注册。这个过程,不需要用户填写信息。因此,他的注册成本大大降低。
+
+
+
+### 课堂8 - 用户手机登录
+
+
+
+
+
+### 课堂8 - 用户中心在线学习
+
+在用户中心中,可以看到自己的学习时间、每个课程的学习进度等等。
+
+
+
+
+### 课堂8 - 修改个人资料
+
+
+
+
+### 课堂8 - 修改个人头像
+
+修改头像中,设计的技术包含了,上传图片、到分布式附件中心,对分布式附件里的图片进行预览和在线剪辑等等功能...
+
+
+
+### 课堂8 - 在线支付购买课程
+
+在线支付环节,看起来内容少,但工作量和细节是巨大的。
+
+- 在 PC 模式下,必须支持 微信支付 和 支付宝支付的选择。
+- 在 微信 里,必须隐藏掉微信支付和支付宝支付的选择方式,只能用微信支付。
+- 在 H5 浏览器里(比如 UC 浏览器),能够选择支付方式,并在支付的时候自动唤起(打开)手机里的支付宝和微信的 APP 进行支付。
+
+
+
+
+
+### 课堂8 - 更多
+
+除此之外,我们还做了很多你看不见的工作:
+
+- 基于阿里云的视频加密播放。
+- 前后台分离部署,前后台分别部署在不同的机器里。
+- 涉及到的Web安全防护,比如 XSS、CSRF 等等。
+- ......
\ No newline at end of file
diff --git a/doc/jbootadmin/features/account.png b/doc/jbootadmin/features/account.png
new file mode 100644
index 0000000000000000000000000000000000000000..dbb7b2be232362b28aee3385d9d612caffccdd76
Binary files /dev/null and b/doc/jbootadmin/features/account.png differ
diff --git a/doc/jbootadmin/features/action_log.jpeg b/doc/jbootadmin/features/action_log.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..664c5b7c2ee067ee732f96c9212cc4ff752ba89e
Binary files /dev/null and b/doc/jbootadmin/features/action_log.jpeg differ
diff --git a/doc/jbootadmin/features/apidoc_code.png b/doc/jbootadmin/features/apidoc_code.png
new file mode 100644
index 0000000000000000000000000000000000000000..946d25db3b0e91a8a67bd6ac1aea752f1285154b
Binary files /dev/null and b/doc/jbootadmin/features/apidoc_code.png differ
diff --git a/doc/jbootadmin/features/apidoc_debug.jpeg b/doc/jbootadmin/features/apidoc_debug.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..adff7e65e044e6abb4b6b838de2563fb4b53aec0
Binary files /dev/null and b/doc/jbootadmin/features/apidoc_debug.jpeg differ
diff --git a/doc/jbootadmin/features/apidoc_info.jpeg b/doc/jbootadmin/features/apidoc_info.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..125dd3a9ec416d78c30597d6bb4399d37f64cdf1
Binary files /dev/null and b/doc/jbootadmin/features/apidoc_info.jpeg differ
diff --git a/doc/jbootadmin/features/apidoc_mock.jpeg b/doc/jbootadmin/features/apidoc_mock.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..cd9c032df9a695086c75066e1cbc67d2a54ce663
Binary files /dev/null and b/doc/jbootadmin/features/apidoc_mock.jpeg differ
diff --git a/doc/jbootadmin/features/build_memu.jpeg b/doc/jbootadmin/features/build_memu.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..607b26fb07c59f38da15bc1e435e04bcf0258823
Binary files /dev/null and b/doc/jbootadmin/features/build_memu.jpeg differ
diff --git a/doc/jbootadmin/features/build_permission.jpeg b/doc/jbootadmin/features/build_permission.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..93cb2a55d7c3e535c774e3c932c4d838838fc269
Binary files /dev/null and b/doc/jbootadmin/features/build_permission.jpeg differ
diff --git a/doc/jbootadmin/features/demo_product.jpeg b/doc/jbootadmin/features/demo_product.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..adb998873fef6ddde251f200b4c445a6b7523d0d
Binary files /dev/null and b/doc/jbootadmin/features/demo_product.jpeg differ
diff --git a/doc/jbootadmin/features/demo_product_edit.jpeg b/doc/jbootadmin/features/demo_product_edit.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..694ae75da40ee76ad017a8c173ae3b663173b423
Binary files /dev/null and b/doc/jbootadmin/features/demo_product_edit.jpeg differ
diff --git a/doc/jbootadmin/features/demo_warehousein.jpeg b/doc/jbootadmin/features/demo_warehousein.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..89dc6a7055fecc8211ea5ccf846a267c811ef31a
Binary files /dev/null and b/doc/jbootadmin/features/demo_warehousein.jpeg differ
diff --git a/doc/jbootadmin/features/demo_warehousein_edit1.jpeg b/doc/jbootadmin/features/demo_warehousein_edit1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..b35b4c39d28cd609c2e46bfc8408fdb7b43ddce8
Binary files /dev/null and b/doc/jbootadmin/features/demo_warehousein_edit1.jpeg differ
diff --git a/doc/jbootadmin/features/demo_warehousein_edit2.jpeg b/doc/jbootadmin/features/demo_warehousein_edit2.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..2308bfcda12b01e549cb3fc6039af0d61628e723
Binary files /dev/null and b/doc/jbootadmin/features/demo_warehousein_edit2.jpeg differ
diff --git a/doc/jbootadmin/features/demo_warehousein_view.jpeg b/doc/jbootadmin/features/demo_warehousein_view.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..58852dbec017a973df1dec5ca443428b0795b3ef
Binary files /dev/null and b/doc/jbootadmin/features/demo_warehousein_view.jpeg differ
diff --git a/doc/jbootadmin/features/dept.jpeg b/doc/jbootadmin/features/dept.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..38e74dee9cf21146ad4229a915a364babcfeac39
Binary files /dev/null and b/doc/jbootadmin/features/dept.jpeg differ
diff --git a/doc/jbootadmin/features/dev_codgen.jpeg b/doc/jbootadmin/features/dev_codgen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..a050dd0e85cd93767d24887f0017a963a8a42c29
Binary files /dev/null and b/doc/jbootadmin/features/dev_codgen.jpeg differ
diff --git a/doc/jbootadmin/features/dev_codgen_edit.jpeg b/doc/jbootadmin/features/dev_codgen_edit.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..39a601fb018434bfb36b37d9b8a0959d51afd095
Binary files /dev/null and b/doc/jbootadmin/features/dev_codgen_edit.jpeg differ
diff --git a/doc/jbootadmin/features/dev_codgen_fields.jpeg b/doc/jbootadmin/features/dev_codgen_fields.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..fc3761f83971997281261b57414ab17137ca7ca0
Binary files /dev/null and b/doc/jbootadmin/features/dev_codgen_fields.jpeg differ
diff --git a/doc/jbootadmin/features/dev_codgen_gen.jpeg b/doc/jbootadmin/features/dev_codgen_gen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..4467b8efdd928667b0aea7420df1559f75f9e654
Binary files /dev/null and b/doc/jbootadmin/features/dev_codgen_gen.jpeg differ
diff --git a/doc/jbootadmin/features/dev_menu_gen.jpeg b/doc/jbootadmin/features/dev_menu_gen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..dfcf02af968f32e54fa752ff5e71a5a029c3d34f
Binary files /dev/null and b/doc/jbootadmin/features/dev_menu_gen.jpeg differ
diff --git a/doc/jbootadmin/features/dev_permission_gen.jpeg b/doc/jbootadmin/features/dev_permission_gen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..0c235f759fa072b7ba038605d0c539f10cb8dbf8
Binary files /dev/null and b/doc/jbootadmin/features/dev_permission_gen.jpeg differ
diff --git a/doc/jbootadmin/features/dev_project.jpeg b/doc/jbootadmin/features/dev_project.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..615fdc39b3e50ee8412dd1ca45c3819913a3be84
Binary files /dev/null and b/doc/jbootadmin/features/dev_project.jpeg differ
diff --git a/doc/jbootadmin/features/dev_project_edit.jpeg b/doc/jbootadmin/features/dev_project_edit.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..737c819e269efbbad055b0ed1071454d1f0e163d
Binary files /dev/null and b/doc/jbootadmin/features/dev_project_edit.jpeg differ
diff --git a/doc/jbootadmin/features/devops_ahas.png b/doc/jbootadmin/features/devops_ahas.png
new file mode 100644
index 0000000000000000000000000000000000000000..c39e62d13e8c76ded274e236446d33b3e5317f49
Binary files /dev/null and b/doc/jbootadmin/features/devops_ahas.png differ
diff --git a/doc/jbootadmin/features/devops_apps.jpeg b/doc/jbootadmin/features/devops_apps.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..d41201949f13128f76de15db865856cc44b6977d
Binary files /dev/null and b/doc/jbootadmin/features/devops_apps.jpeg differ
diff --git a/doc/jbootadmin/features/devops_cache.jpeg b/doc/jbootadmin/features/devops_cache.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..8eeb421f5b012f4ed859d9eb8425c04e9f596a02
Binary files /dev/null and b/doc/jbootadmin/features/devops_cache.jpeg differ
diff --git a/doc/jbootadmin/features/devops_d.jpeg b/doc/jbootadmin/features/devops_d.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..b18971a9d2a663c6f77f940473328ff004a1c93e
Binary files /dev/null and b/doc/jbootadmin/features/devops_d.jpeg differ
diff --git a/doc/jbootadmin/features/devops_gateway_nacos1.jpeg b/doc/jbootadmin/features/devops_gateway_nacos1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..0ec1ff19ec042b8a087449840f6b72964b9a5497
Binary files /dev/null and b/doc/jbootadmin/features/devops_gateway_nacos1.jpeg differ
diff --git a/doc/jbootadmin/features/devops_gateway_nacos2.jpeg b/doc/jbootadmin/features/devops_gateway_nacos2.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..869dc72d03f58ad57d2f5ff9908a387c29c8234e
Binary files /dev/null and b/doc/jbootadmin/features/devops_gateway_nacos2.jpeg differ
diff --git a/doc/jbootadmin/features/devops_grafana_jboot_jvm.png b/doc/jbootadmin/features/devops_grafana_jboot_jvm.png
new file mode 100644
index 0000000000000000000000000000000000000000..c9e29539cf6291675207124cdddaa0e1c9bcef45
Binary files /dev/null and b/doc/jbootadmin/features/devops_grafana_jboot_jvm.png differ
diff --git a/doc/jbootadmin/features/ketang8_buy.jpeg b/doc/jbootadmin/features/ketang8_buy.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..c0afce2fe235fe7ef835391c523a58208c39fd6c
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_buy.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_buy1.png b/doc/jbootadmin/features/ketang8_buy1.png
new file mode 100644
index 0000000000000000000000000000000000000000..b178e66f273eb8da92bf28a16564208d1c6d59e2
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_buy1.png differ
diff --git a/doc/jbootadmin/features/ketang8_course_detail.jpeg b/doc/jbootadmin/features/ketang8_course_detail.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..b33aaf9f7dde907ce895f61219d4ebd943252dbc
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_course_detail.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_course_study.jpeg b/doc/jbootadmin/features/ketang8_course_study.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..57ee184b719665f6efecbcbe1de9a6ff2bdbecfa
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_course_study.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_course_study2.jpeg b/doc/jbootadmin/features/ketang8_course_study2.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..52daa47b386a46958c5d897e6755a50e94e40a14
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_course_study2.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_index.jpeg b/doc/jbootadmin/features/ketang8_index.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..7b8e3f9389724083556d548109f0c0d2087b3c79
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_index.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_login_mobile1.jpeg b/doc/jbootadmin/features/ketang8_login_mobile1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..8baaa1576e910e9f64a611639e0d62471a16609d
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_login_mobile1.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_login_mobile2.jpeg b/doc/jbootadmin/features/ketang8_login_mobile2.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..857e16177689006a178446c1695adecd4add0dbd
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_login_mobile2.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_login_wechat.jpeg b/doc/jbootadmin/features/ketang8_login_wechat.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..f2ecae1ccc942b1a84a5beb031ff021ade6c79c2
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_login_wechat.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_ucenter_avatar.jpeg b/doc/jbootadmin/features/ketang8_ucenter_avatar.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..1c086f7f71cc7168346431cb1e2e0ae1059f32d5
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_ucenter_avatar.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_ucenter_modify.jpeg b/doc/jbootadmin/features/ketang8_ucenter_modify.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..17d5e0809c5119b79cafa375066f6f8ad7495bb2
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_ucenter_modify.jpeg differ
diff --git a/doc/jbootadmin/features/ketang8_ucenter_studied.jpeg b/doc/jbootadmin/features/ketang8_ucenter_studied.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..608d19603f4ead4c1fc247318a26e5e43152e217
Binary files /dev/null and b/doc/jbootadmin/features/ketang8_ucenter_studied.jpeg differ
diff --git a/doc/jbootadmin/features/role.jpeg b/doc/jbootadmin/features/role.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..ac1ae47f0f9e418cd6e34c202912a7192677aa7d
Binary files /dev/null and b/doc/jbootadmin/features/role.jpeg differ
diff --git a/doc/jbootadmin/features/role_permission.jpeg b/doc/jbootadmin/features/role_permission.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..dc47fbffb4b3f0d841505b52025367d06ba5f282
Binary files /dev/null and b/doc/jbootadmin/features/role_permission.jpeg differ
diff --git a/doc/jbootadmin/features/station.jpeg b/doc/jbootadmin/features/station.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..79ff0d795a6544ce1e59e9f15b15e962754e93d8
Binary files /dev/null and b/doc/jbootadmin/features/station.jpeg differ
diff --git a/doc/jbootadmin/features/sys_area.jpeg b/doc/jbootadmin/features/sys_area.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..a88d80713f619a48484700d75f6f5af8d66914e6
Binary files /dev/null and b/doc/jbootadmin/features/sys_area.jpeg differ
diff --git a/doc/jbootadmin/features/sys_dict.jpeg b/doc/jbootadmin/features/sys_dict.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..d44fae72b9d2f2c73f3476d00b072aeceacbe11c
Binary files /dev/null and b/doc/jbootadmin/features/sys_dict.jpeg differ
diff --git a/doc/jbootadmin/features/sys_dict_edit.jpeg b/doc/jbootadmin/features/sys_dict_edit.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..75475206ff244d217b274179639ed5e8fb7d5815
Binary files /dev/null and b/doc/jbootadmin/features/sys_dict_edit.jpeg differ
diff --git a/doc/jbootadmin/features/sys_option.jpeg b/doc/jbootadmin/features/sys_option.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..9c6b930a2a67876d33467d414f774feebc792641
Binary files /dev/null and b/doc/jbootadmin/features/sys_option.jpeg differ
diff --git a/doc/jbootadmin/features/wechat_account.jpeg b/doc/jbootadmin/features/wechat_account.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..6a4dd9a24d42f1c0e026a18da8516d32b3c0c15b
Binary files /dev/null and b/doc/jbootadmin/features/wechat_account.jpeg differ
diff --git a/doc/jbootadmin/features/wechat_keyword.jpeg b/doc/jbootadmin/features/wechat_keyword.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..91fb3e2b9fb51e5a614c27ec8ca5a3b4a449c0ea
Binary files /dev/null and b/doc/jbootadmin/features/wechat_keyword.jpeg differ
diff --git a/doc/jbootadmin/features/wechat_keyword_config.jpeg b/doc/jbootadmin/features/wechat_keyword_config.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..cadab2cdb0c6f3e7e8be693955d34e22381c9e10
Binary files /dev/null and b/doc/jbootadmin/features/wechat_keyword_config.jpeg differ
diff --git a/doc/jbootadmin/features/wechat_reply.jpeg b/doc/jbootadmin/features/wechat_reply.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..5400f7019e462eee675536b2de3c650a467b184a
Binary files /dev/null and b/doc/jbootadmin/features/wechat_reply.jpeg differ
diff --git a/doc/jbootadmin/front.md b/doc/jbootadmin/front.md
new file mode 100644
index 0000000000000000000000000000000000000000..6c3b3fe9cb78e79a0a86f77c9997c9d521cf5d81
--- /dev/null
+++ b/doc/jbootadmin/front.md
@@ -0,0 +1,133 @@
+# 前端组件
+
+JbootAdmin 内置了大量的组件,我们几乎不需要编写任何的 JavaScript 代码就可以轻松的使用这些前段组件。
+
+## Layer
+
+Layer 是一个功能强大的弹出层,弹出层里既可以通过一个 Div 来渲染,也可以通过一个 IFrame 来渲染弹出。
+
+示例:
+
+```html
+ 示例
+```
+
+当我们点击该 `a` 标签链接时,此链接将以弹窗的方式访问 `user.html` 。若是 `input` 或者 `textarea` 等添加了 `open-type="layer"` 属性,那么当 `input` 或者 `textarea` 获得焦点的时候,会自动打开弹窗 Layer。
+
+
+在组件中,可以配置的属性如下:
+
+
+- data-layer-type : 打开 layer 的类型
+- data-layer-title : 打开 layer 的标题
+- data-layer-anim : 打开 layer 的动画
+- data-layer-shade-close
+- data-layer-shade
+- data-layer-area
+- data-layer-content
+- data-layer-binds : layer 关闭后,对数据进行绑定
+- data-layer-end : 当 layer 关闭后,执行的方法
+
+## Switchery 开关
+
+
+
+```html
+
+```
+
+其支持的属性内容如下:
+
+- for: 自动同步 switchery 的值到其他的 input
+- data-ctrl: 用于控制某个 div 显示或隐藏
+- data-open-sync :当此 switchery 开启的时候,自动同步到其他的 switchery 一起开启。
+- data-close-sync : 当此 switchery 关闭的时候,自动同步其他的 switchery 一起关闭。
+- data-change-function : 当此 switchery 点击开启或者关闭时执行的方法
+
+
+## 确认操作弹出组件
+
+在很多场景下,在用户要进行某个操作之前,我们需要弹出一个对话框让用户进行确定。比如我们要删除某个数据,我们需要弹出一个确认框让用户确认之后,在进行下一步的删除行动。
+
+
+```html
+
+```
+
+或者 如下代码用于 删除数据 的弹出确认。
+
+```html
+
+```
+
+
+
+其支持的属性内容如下:
+
+- data-title : 弹出确认框标题
+- data-text: 弹出确认框内容
+- data-btn-text: 弹出确认框按钮内容
+- data-success-title : 成功弹出标题
+- data-success-text: 成功弹出内容
+- data-success-function : ajax 提交数据成功执行的方法
+- data-success-goto: ajax 执行数据成功后,跳转到的页面url地址
+- data-success-message: ajax 支持成功后,弹出提示内容
+- data-fail-function: ajax 提交失败后,执行的 js 方法
+- data-fail-message: ajax 提交失败后,弹出的提示内容。
+
+
+## 链接自动 Ajax 提交
+
+```html
+
+```
+
+当 `a` 标签有 `open-type="ajax"` 时,我们点击 `a` 标签的时候,自动会自动以 ajax 方式提交到 `/href/to/your/path` ,但是往往我们需要执行完成 ajax 提交后,会执行某些动作,此时需要添加
+
+`data-success-function` 属性。
+
+```html
+
+
+
+
+
+
+
+
+
WebSocket Chat
+
+
+
+
+
+
+
+
+
+