From bf81536e0ea943fba08738ace16935f6cc6a32f0 Mon Sep 17 00:00:00 2001 From: eduosi Date: Mon, 27 Nov 2023 11:34:56 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E9=83=A8=E7=BD=B2=E3=80=91=EF=BC=9A20?= =?UTF-8?q?23-11-27=2011:34:56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CNAME | 1 + about/index.html | 4 + docs/guide.md | 1 + docs/index.md | 24 + docs/installation.html | 37 + docs/intro.html | 38 + docs/license.html | 209 + docs/module.html | 103 + docs/plan.html | 24 + docs/quickstart.html | 33 + docs/requirement.html | 40 + docs/version.html | 26 + index.html | 16 + js/stat.js | 7 + manual/2.0/aop/index.html | 27 + manual/2.0/beans/index.html | 93 + manual/2.0/core/builder.html | 140 + manual/2.0/core/codec.html | 82 + manual/2.0/core/collect.html | 160 + manual/2.0/core/context.html | 91 + manual/2.0/core/converter.html | 155 + manual/2.0/core/datetime.html | 33 + manual/2.0/core/exception.html | 70 + manual/2.0/core/id.html | 101 + manual/2.0/core/index.html | 53 + manual/2.0/core/math.html | 67 + manual/2.0/core/other.html | 101 + manual/2.0/core/serializer.html | 56 + manual/2.0/core/utils.html | 199 + manual/2.0/core/validator.html | 239 + manual/2.0/cron/index.html | 30 + manual/2.0/dao/index.html | 46 + manual/2.0/dao/mongodb.html | 24 + manual/2.0/dao/mybatis.html | 170 + manual/2.0/geoip/index.html | 79 + manual/2.0/httpclient/configuration.html | 139 + manual/2.0/httpclient/connectionmanager.html | 23 + manual/2.0/httpclient/index.html | 115 + manual/2.0/httpclient/method.html | 158 + manual/2.0/httpclient/response.html | 37 + manual/2.0/index.html | 114 + manual/2.0/io/index.html | 86 + manual/2.0/jdbc/index.html | 28 + manual/2.0/json/index.html | 63 + manual/2.0/lang/index.html | 27 + manual/2.0/net/index.html | 35 + manual/2.0/redis/datasource.html | 185 + manual/2.0/redis/index.html | 51 + manual/2.0/redis/method.html | 49 + manual/2.0/session/index.html | 28 + manual/2.0/thesaurus/index.html | 37 + manual/2.0/velocity/index.html | 28 + manual/2.0/web/annotation.html | 74 + manual/2.0/web/filter.html | 55 + manual/2.0/web/index.html | 28 + manual/2.0/web/restful.html | 47 + manual/2.0/web/utils.html | 35 + manual/2.1/aop/index.html | 27 + manual/2.1/beans/index.html | 93 + manual/2.1/core/builder.html | 140 + manual/2.1/core/codec.html | 82 + manual/2.1/core/collect.html | 160 + manual/2.1/core/context.html | 91 + manual/2.1/core/converter.html | 159 + manual/2.1/core/datetime.html | 33 + manual/2.1/core/exception.html | 70 + manual/2.1/core/id.html | 101 + manual/2.1/core/index.html | 53 + manual/2.1/core/math.html | 67 + manual/2.1/core/other.html | 101 + manual/2.1/core/serializer.html | 56 + manual/2.1/core/utils.html | 199 + manual/2.1/core/validator.html | 239 + manual/2.1/cron/index.html | 30 + manual/2.1/dao/index.html | 46 + manual/2.1/dao/mongodb.html | 24 + manual/2.1/dao/mybatis.html | 170 + manual/2.1/geoip/index.html | 79 + manual/2.1/httpclient/configuration.html | 139 + manual/2.1/httpclient/connectionmanager.html | 23 + manual/2.1/httpclient/index.html | 115 + manual/2.1/httpclient/method.html | 158 + manual/2.1/httpclient/response.html | 37 + manual/2.1/index.html | 119 + manual/2.1/io/index.html | 86 + manual/2.1/jdbc/index.html | 28 + manual/2.1/json/index.html | 63 + manual/2.1/lang/index.html | 27 + manual/2.1/net/index.html | 35 + manual/2.1/redis/datasource.html | 185 + manual/2.1/redis/index.html | 51 + manual/2.1/redis/method.html | 49 + manual/2.1/session/index.html | 28 + manual/2.1/thesaurus/index.html | 37 + manual/2.1/velocity/index.html | 28 + manual/2.1/web/annotation.html | 152 + manual/2.1/web/filter.html | 51 + manual/2.1/web/index.html | 28 + manual/2.1/web/restful.html | 47 + manual/2.1/web/utils.html | 35 + manual/2.2/aop/index.html | 27 + manual/2.2/beans/index.html | 93 + manual/2.2/core/builder.html | 140 + manual/2.2/core/codec.html | 82 + manual/2.2/core/collect.html | 160 + manual/2.2/core/context.html | 91 + manual/2.2/core/converter.html | 159 + manual/2.2/core/datetime.html | 33 + manual/2.2/core/exception.html | 70 + manual/2.2/core/id.html | 101 + manual/2.2/core/index.html | 53 + manual/2.2/core/math.html | 67 + manual/2.2/core/other.html | 101 + manual/2.2/core/serializer.html | 56 + manual/2.2/core/utils.html | 199 + manual/2.2/core/validator.html | 239 + manual/2.2/cron/index.html | 30 + manual/2.2/dao/index.html | 46 + manual/2.2/dao/mongodb.html | 24 + manual/2.2/dao/mybatis.html | 170 + manual/2.2/geoip/index.html | 79 + manual/2.2/git/index.html | 27 + manual/2.2/httpclient/configuration.html | 139 + manual/2.2/httpclient/connectionmanager.html | 23 + manual/2.2/httpclient/index.html | 115 + manual/2.2/httpclient/method.html | 158 + manual/2.2/httpclient/response.html | 37 + manual/2.2/index.html | 114 + manual/2.2/io/index.html | 86 + manual/2.2/jdbc/index.html | 28 + manual/2.2/json/index.html | 63 + manual/2.2/lang/index.html | 27 + manual/2.2/net/index.html | 35 + manual/2.2/redis/datasource.html | 185 + manual/2.2/redis/index.html | 51 + manual/2.2/redis/method.html | 49 + manual/2.2/session/index.html | 28 + manual/2.2/thesaurus/index.html | 37 + manual/2.2/velocity/index.html | 28 + manual/2.2/web/annotation.html | 152 + manual/2.2/web/filter.html | 51 + manual/2.2/web/index.html | 28 + manual/2.2/web/restful.html | 47 + manual/2.2/web/utils.html | 35 + manual/2.3/aop/index.html | 27 + manual/2.3/beans/index.html | 93 + manual/2.3/core/builder.html | 140 + manual/2.3/core/codec.html | 82 + manual/2.3/core/collect.html | 160 + manual/2.3/core/configurer.html | 46 + manual/2.3/core/context.html | 91 + manual/2.3/core/converter.html | 159 + manual/2.3/core/customizer.html | 46 + manual/2.3/core/datetime.html | 33 + manual/2.3/core/deserializer.html | 54 + manual/2.3/core/exception.html | 70 + manual/2.3/core/id.html | 101 + manual/2.3/core/index.html | 59 + manual/2.3/core/math.html | 67 + manual/2.3/core/other.html | 101 + manual/2.3/core/serializer.html | 52 + manual/2.3/core/utils.html | 199 + manual/2.3/core/validator.html | 239 + manual/2.3/cron/index.html | 30 + manual/2.3/dao/index.html | 46 + manual/2.3/dao/mongodb.html | 24 + manual/2.3/dao/mybatis.html | 170 + manual/2.3/geoip/index.html | 79 + manual/2.3/git/index.html | 27 + manual/2.3/httpclient/configuration.html | 139 + manual/2.3/httpclient/connectionmanager.html | 23 + manual/2.3/httpclient/index.html | 113 + manual/2.3/httpclient/method.html | 158 + manual/2.3/httpclient/response.html | 37 + manual/2.3/index.html | 114 + manual/2.3/io/index.html | 86 + manual/2.3/jdbc/index.html | 28 + manual/2.3/json/index.html | 63 + manual/2.3/lang/index.html | 27 + manual/2.3/net/index.html | 35 + manual/2.3/redis/datasource.html | 185 + manual/2.3/redis/index.html | 51 + manual/2.3/redis/method.html | 49 + manual/2.3/session/index.html | 28 + manual/2.3/thesaurus/index.html | 37 + manual/2.3/velocity/index.html | 28 + manual/2.3/web/annotation.html | 152 + manual/2.3/web/filter.html | 51 + manual/2.3/web/index.html | 28 + manual/2.3/web/pagination.html | 177 + manual/2.3/web/response.html | 263 ++ manual/2.3/web/restful.html | 47 + manual/2.3/web/utils.html | 35 + manual/index.html | 19 + manual/overview.html | 44 + search_json.js | 4294 ++++++++++++++++++ support.html | 16 + ydoc/images/android-chrome-192x192.png | Bin 0 -> 11657 bytes ydoc/images/android-chrome-512x512.png | Bin 0 -> 34589 bytes ydoc/images/apple-touch-icon.png | Bin 0 -> 10867 bytes ydoc/images/browserconfig.xml | 9 + ydoc/images/dog@1x.png | Bin 0 -> 2368 bytes ydoc/images/dog@2x.png | Bin 0 -> 6266 bytes ydoc/images/dogbg@1x.png | Bin 0 -> 35590 bytes ydoc/images/dogbg@2x.png | Bin 0 -> 93633 bytes ydoc/images/favicon-16x16.png | Bin 0 -> 1253 bytes ydoc/images/favicon-32x32.png | Bin 0 -> 1784 bytes ydoc/images/favicon.ico | Bin 0 -> 15086 bytes ydoc/images/logo.png | Bin 0 -> 23290 bytes ydoc/images/manifest.json | 19 + ydoc/images/mstile-150x150.png | Bin 0 -> 8361 bytes ydoc/images/safari-pinned-tab.svg | 70 + ydoc/scripts/app.js | 87 + ydoc/scripts/plugins/dollar.min.js | 6 + ydoc/scripts/plugins/responsive-nav.min.js | 1 + ydoc/scripts/plugins/slideout.min.js | 1 + ydoc/styles/style.css | 2281 ++++++++++ ydoc/ydoc-plugin-search/core.js | 104 + ydoc/ydoc-plugin-search/search.css | 118 + ydoc/ydoc-plugin-search/search.js | 127 + 220 files changed, 22470 insertions(+) create mode 100644 CNAME create mode 100644 about/index.html create mode 100644 docs/guide.md create mode 100644 docs/index.md create mode 100644 docs/installation.html create mode 100644 docs/intro.html create mode 100644 docs/license.html create mode 100644 docs/module.html create mode 100644 docs/plan.html create mode 100644 docs/quickstart.html create mode 100644 docs/requirement.html create mode 100644 docs/version.html create mode 100644 index.html create mode 100644 js/stat.js create mode 100644 manual/2.0/aop/index.html create mode 100644 manual/2.0/beans/index.html create mode 100644 manual/2.0/core/builder.html create mode 100644 manual/2.0/core/codec.html create mode 100644 manual/2.0/core/collect.html create mode 100644 manual/2.0/core/context.html create mode 100644 manual/2.0/core/converter.html create mode 100644 manual/2.0/core/datetime.html create mode 100644 manual/2.0/core/exception.html create mode 100644 manual/2.0/core/id.html create mode 100644 manual/2.0/core/index.html create mode 100644 manual/2.0/core/math.html create mode 100644 manual/2.0/core/other.html create mode 100644 manual/2.0/core/serializer.html create mode 100644 manual/2.0/core/utils.html create mode 100644 manual/2.0/core/validator.html create mode 100644 manual/2.0/cron/index.html create mode 100644 manual/2.0/dao/index.html create mode 100644 manual/2.0/dao/mongodb.html create mode 100644 manual/2.0/dao/mybatis.html create mode 100644 manual/2.0/geoip/index.html create mode 100644 manual/2.0/httpclient/configuration.html create mode 100644 manual/2.0/httpclient/connectionmanager.html create mode 100644 manual/2.0/httpclient/index.html create mode 100644 manual/2.0/httpclient/method.html create mode 100644 manual/2.0/httpclient/response.html create mode 100644 manual/2.0/index.html create mode 100644 manual/2.0/io/index.html create mode 100644 manual/2.0/jdbc/index.html create mode 100644 manual/2.0/json/index.html create mode 100644 manual/2.0/lang/index.html create mode 100644 manual/2.0/net/index.html create mode 100644 manual/2.0/redis/datasource.html create mode 100644 manual/2.0/redis/index.html create mode 100644 manual/2.0/redis/method.html create mode 100644 manual/2.0/session/index.html create mode 100644 manual/2.0/thesaurus/index.html create mode 100644 manual/2.0/velocity/index.html create mode 100644 manual/2.0/web/annotation.html create mode 100644 manual/2.0/web/filter.html create mode 100644 manual/2.0/web/index.html create mode 100644 manual/2.0/web/restful.html create mode 100644 manual/2.0/web/utils.html create mode 100644 manual/2.1/aop/index.html create mode 100644 manual/2.1/beans/index.html create mode 100644 manual/2.1/core/builder.html create mode 100644 manual/2.1/core/codec.html create mode 100644 manual/2.1/core/collect.html create mode 100644 manual/2.1/core/context.html create mode 100644 manual/2.1/core/converter.html create mode 100644 manual/2.1/core/datetime.html create mode 100644 manual/2.1/core/exception.html create mode 100644 manual/2.1/core/id.html create mode 100644 manual/2.1/core/index.html create mode 100644 manual/2.1/core/math.html create mode 100644 manual/2.1/core/other.html create mode 100644 manual/2.1/core/serializer.html create mode 100644 manual/2.1/core/utils.html create mode 100644 manual/2.1/core/validator.html create mode 100644 manual/2.1/cron/index.html create mode 100644 manual/2.1/dao/index.html create mode 100644 manual/2.1/dao/mongodb.html create mode 100644 manual/2.1/dao/mybatis.html create mode 100644 manual/2.1/geoip/index.html create mode 100644 manual/2.1/httpclient/configuration.html create mode 100644 manual/2.1/httpclient/connectionmanager.html create mode 100644 manual/2.1/httpclient/index.html create mode 100644 manual/2.1/httpclient/method.html create mode 100644 manual/2.1/httpclient/response.html create mode 100644 manual/2.1/index.html create mode 100644 manual/2.1/io/index.html create mode 100644 manual/2.1/jdbc/index.html create mode 100644 manual/2.1/json/index.html create mode 100644 manual/2.1/lang/index.html create mode 100644 manual/2.1/net/index.html create mode 100644 manual/2.1/redis/datasource.html create mode 100644 manual/2.1/redis/index.html create mode 100644 manual/2.1/redis/method.html create mode 100644 manual/2.1/session/index.html create mode 100644 manual/2.1/thesaurus/index.html create mode 100644 manual/2.1/velocity/index.html create mode 100644 manual/2.1/web/annotation.html create mode 100644 manual/2.1/web/filter.html create mode 100644 manual/2.1/web/index.html create mode 100644 manual/2.1/web/restful.html create mode 100644 manual/2.1/web/utils.html create mode 100644 manual/2.2/aop/index.html create mode 100644 manual/2.2/beans/index.html create mode 100644 manual/2.2/core/builder.html create mode 100644 manual/2.2/core/codec.html create mode 100644 manual/2.2/core/collect.html create mode 100644 manual/2.2/core/context.html create mode 100644 manual/2.2/core/converter.html create mode 100644 manual/2.2/core/datetime.html create mode 100644 manual/2.2/core/exception.html create mode 100644 manual/2.2/core/id.html create mode 100644 manual/2.2/core/index.html create mode 100644 manual/2.2/core/math.html create mode 100644 manual/2.2/core/other.html create mode 100644 manual/2.2/core/serializer.html create mode 100644 manual/2.2/core/utils.html create mode 100644 manual/2.2/core/validator.html create mode 100644 manual/2.2/cron/index.html create mode 100644 manual/2.2/dao/index.html create mode 100644 manual/2.2/dao/mongodb.html create mode 100644 manual/2.2/dao/mybatis.html create mode 100644 manual/2.2/geoip/index.html create mode 100644 manual/2.2/git/index.html create mode 100644 manual/2.2/httpclient/configuration.html create mode 100644 manual/2.2/httpclient/connectionmanager.html create mode 100644 manual/2.2/httpclient/index.html create mode 100644 manual/2.2/httpclient/method.html create mode 100644 manual/2.2/httpclient/response.html create mode 100644 manual/2.2/index.html create mode 100644 manual/2.2/io/index.html create mode 100644 manual/2.2/jdbc/index.html create mode 100644 manual/2.2/json/index.html create mode 100644 manual/2.2/lang/index.html create mode 100644 manual/2.2/net/index.html create mode 100644 manual/2.2/redis/datasource.html create mode 100644 manual/2.2/redis/index.html create mode 100644 manual/2.2/redis/method.html create mode 100644 manual/2.2/session/index.html create mode 100644 manual/2.2/thesaurus/index.html create mode 100644 manual/2.2/velocity/index.html create mode 100644 manual/2.2/web/annotation.html create mode 100644 manual/2.2/web/filter.html create mode 100644 manual/2.2/web/index.html create mode 100644 manual/2.2/web/restful.html create mode 100644 manual/2.2/web/utils.html create mode 100644 manual/2.3/aop/index.html create mode 100644 manual/2.3/beans/index.html create mode 100644 manual/2.3/core/builder.html create mode 100644 manual/2.3/core/codec.html create mode 100644 manual/2.3/core/collect.html create mode 100644 manual/2.3/core/configurer.html create mode 100644 manual/2.3/core/context.html create mode 100644 manual/2.3/core/converter.html create mode 100644 manual/2.3/core/customizer.html create mode 100644 manual/2.3/core/datetime.html create mode 100644 manual/2.3/core/deserializer.html create mode 100644 manual/2.3/core/exception.html create mode 100644 manual/2.3/core/id.html create mode 100644 manual/2.3/core/index.html create mode 100644 manual/2.3/core/math.html create mode 100644 manual/2.3/core/other.html create mode 100644 manual/2.3/core/serializer.html create mode 100644 manual/2.3/core/utils.html create mode 100644 manual/2.3/core/validator.html create mode 100644 manual/2.3/cron/index.html create mode 100644 manual/2.3/dao/index.html create mode 100644 manual/2.3/dao/mongodb.html create mode 100644 manual/2.3/dao/mybatis.html create mode 100644 manual/2.3/geoip/index.html create mode 100644 manual/2.3/git/index.html create mode 100644 manual/2.3/httpclient/configuration.html create mode 100644 manual/2.3/httpclient/connectionmanager.html create mode 100644 manual/2.3/httpclient/index.html create mode 100644 manual/2.3/httpclient/method.html create mode 100644 manual/2.3/httpclient/response.html create mode 100644 manual/2.3/index.html create mode 100644 manual/2.3/io/index.html create mode 100644 manual/2.3/jdbc/index.html create mode 100644 manual/2.3/json/index.html create mode 100644 manual/2.3/lang/index.html create mode 100644 manual/2.3/net/index.html create mode 100644 manual/2.3/redis/datasource.html create mode 100644 manual/2.3/redis/index.html create mode 100644 manual/2.3/redis/method.html create mode 100644 manual/2.3/session/index.html create mode 100644 manual/2.3/thesaurus/index.html create mode 100644 manual/2.3/velocity/index.html create mode 100644 manual/2.3/web/annotation.html create mode 100644 manual/2.3/web/filter.html create mode 100644 manual/2.3/web/index.html create mode 100644 manual/2.3/web/pagination.html create mode 100644 manual/2.3/web/response.html create mode 100644 manual/2.3/web/restful.html create mode 100644 manual/2.3/web/utils.html create mode 100644 manual/index.html create mode 100644 manual/overview.html create mode 100644 search_json.js create mode 100644 support.html create mode 100644 ydoc/images/android-chrome-192x192.png create mode 100644 ydoc/images/android-chrome-512x512.png create mode 100644 ydoc/images/apple-touch-icon.png create mode 100644 ydoc/images/browserconfig.xml create mode 100644 ydoc/images/dog@1x.png create mode 100644 ydoc/images/dog@2x.png create mode 100644 ydoc/images/dogbg@1x.png create mode 100644 ydoc/images/dogbg@2x.png create mode 100644 ydoc/images/favicon-16x16.png create mode 100644 ydoc/images/favicon-32x32.png create mode 100644 ydoc/images/favicon.ico create mode 100644 ydoc/images/logo.png create mode 100644 ydoc/images/manifest.json create mode 100644 ydoc/images/mstile-150x150.png create mode 100644 ydoc/images/safari-pinned-tab.svg create mode 100644 ydoc/scripts/app.js create mode 100644 ydoc/scripts/plugins/dollar.min.js create mode 100644 ydoc/scripts/plugins/responsive-nav.min.js create mode 100644 ydoc/scripts/plugins/slideout.min.js create mode 100644 ydoc/styles/style.css create mode 100644 ydoc/ydoc-plugin-search/core.js create mode 100644 ydoc/ydoc-plugin-search/search.css create mode 100644 ydoc/ydoc-plugin-search/search.js diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..081e4a1 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.buession.com diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..c8de553 --- /dev/null +++ b/about/index.html @@ -0,0 +1,4 @@ +

About

+

+ ... +

\ No newline at end of file diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000..7f6834d --- /dev/null +++ b/docs/guide.md @@ -0,0 +1 @@ +文档完善中 \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..c7d16dc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,24 @@ +# 框架介绍 + + +### Buession Framework 框架是什么? +Buession Framework 框架不是重复造车轮,它不是其它框架的替代品。 + +#### 它是基于各开源框架的日常工作中常见的通用技术需求二次封装 +1. 本地化的数据验证,如:QQ、电话号码、身份证号码、邮政编码 +2. 常用 DAO 层操作,如:插入、替换、根据主键获取记录、获取单条记录、获取多条记录 +3. 应用层实现数据库读写分离 +4. redis 操作兼容原生 API 的前提下,同时实现了 redis 中的值反序列化成对象 +5. 词库解析(目前仅支持搜狗词库) +6. 使用 WEB 功能,如:响应头注解、缓存头注解、兼容性获取用户端真实 IP、获取用户真实 IP 注解 +7. 替代 springfamework 5,支持 apache velocity +8. 基于 maxmind geoip 的 IP 信息解析 +9. 基于标准的 HTTP 请求方法的 HttpClient +10. 文件操作,如:写文件、设置文件所属用户或组、文件 MimeType 解析 + +... ... + +#### 它是同类开源框架的一种兼容性的上层封装,简化框架切换带来的成本 +1. 摒弃直接使用原生类库,带来的大量的代码修改,如:HttpClient 支持 apache httpcomponents 和 okhttp3,只需要修改 HttpClient 初始化类,即可实现 HTTP 库的切换 + +... ... \ No newline at end of file diff --git a/docs/installation.html b/docs/installation.html new file mode 100644 index 0000000..7ec3ac9 --- /dev/null +++ b/docs/installation.html @@ -0,0 +1,37 @@ +安装及使用-文档

安装及使用

+

Maven 中央仓库搜索

+ +

手动编译

+
git clone https://github.com/buession/buessionframework
+cd buessionframework/buession-parent && mvn clean install
+
+

Maven

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-xxx</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

Gradle

+
compile group: 'com.buession', name: 'buession-xxx', version: 'x.x.x'
+
+

其中,artifactId 中的 xxx 表示对应的子模块;version 中的 x.x.x 代表版本号,根据需要使用特定版本,建议使用 maven 仓库中已构建好的最新版本Maven Central的包。

+
\ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html new file mode 100644 index 0000000..6359ac2 --- /dev/null +++ b/docs/intro.html @@ -0,0 +1,38 @@ +框架介绍-文档

框架介绍

+

Buession Framework 框架是什么?

+

Buession Framework 框架不是重复造车轮,它不是其它框架的替代品。

+

它是基于各开源框架的日常工作中常见的通用技术需求二次封装

+
    +
  1. 本地化的数据验证,如:QQ、电话号码、身份证号码、邮政编码
  2. +
  3. 常用 DAO 层操作,如:插入、替换、根据主键获取记录、获取单条记录、获取多条记录
  4. +
  5. 应用层实现数据库读写分离
  6. +
  7. redis 操作兼容原生 API 的前提下,同时实现了 redis 中的值反序列化成对象
  8. +
  9. 词库解析(目前仅支持搜狗词库)
  10. +
  11. 使用 WEB 功能,如:响应头注解、缓存头注解、兼容性获取用户端真实 IP、获取用户真实 IP 注解
  12. +
  13. 替代 springfamework 5,支持 apache velocity
  14. +
  15. 基于 maxmind geoip 的 IP 信息解析
  16. +
  17. 基于标准的 HTTP 请求方法的 HttpClient
  18. +
  19. 文件操作,如:写文件、设置文件所属用户或组、文件 MimeType 解析
  20. +
+

... ...

+

它是同类开源框架的一种兼容性的上层封装,简化框架切换带来的成本

+
    +
  1. 摒弃直接使用原生类库,带来的大量的代码修改,如:HttpClient 支持 apache httpcomponents 和 okhttp3,只需要修改 HttpClient 初始化类,即可实现 HTTP 库的切换
  2. +
+

... ...

+
\ No newline at end of file diff --git a/docs/license.html b/docs/license.html new file mode 100644 index 0000000..cb7d8de --- /dev/null +++ b/docs/license.html @@ -0,0 +1,209 @@ +开源协议-文档

开源协议

+
                             Apache License
+                       Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+
    +
  1. +

    Definitions.

    +

    "License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document.

    +

    "Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License.

    +

    "Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity.

    +

    "You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License.

    +

    "Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files.

    +

    "Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types.

    +

    "Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below).

    +

    "Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof.

    +

    "Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution."

    +

    "Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work.

    +
  2. +
  3. +

    Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form.

    +
  4. +
  5. +

    Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed.

    +
  6. +
  7. +

    Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions:

    +

    (a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and

    +

    (b) You must cause any modified files to carry prominent notices +stating that You changed the files; and

    +

    (c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and

    +

    (d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License.

    +

    You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License.

    +
  8. +
  9. +

    Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions.

    +
  10. +
  11. +

    Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file.

    +
  12. +
  13. +

    Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License.

    +
  14. +
  15. +

    Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages.

    +
  16. +
  17. +

    Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability.

    +
  18. +
+

END OF TERMS AND CONDITIONS

+

APPENDIX: How to apply the Apache License to your work.

+
  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+

Copyright [yyyy] [name of copyright owner]

+

Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at

+
   http://www.apache.org/licenses/LICENSE-2.0
+
+

Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.

+
\ No newline at end of file diff --git a/docs/module.html b/docs/module.html new file mode 100644 index 0000000..a23cfbf --- /dev/null +++ b/docs/module.html @@ -0,0 +1,103 @@ +模块说明-文档

模块说明

+

buession-aop

+
    +
  • AOP 封装,方便实现自定义注解
  • +
+

buession-beans

+
    +
  • Bean 工具类封
  • +
+

buession-core

+
    +
  • 一些继承 apache lang3、apache collections4 的对字符串、集合、List、Map、Class、Object、Enum、Number等工具类封装和扩展
  • +
  • 汉字拼音工具类
  • +
  • 版本对比工具类
  • +
  • 数学计算
  • +
  • IP 地址工具类
  • +
  • 进制转换
  • +
  • 对象序列化和反序列化,支持二进制、FastJson、Gson、Jackson
  • +
  • 数据合法性验证类
  • +
  • 带 code 和 message 消息消息对象,通过 properties 的形式注入
  • +
  • 日期对象类
  • +
  • ID 生成器
  • +
  • Manager 层注解
  • +
+

buession-cron

+
    +
  • 对 quartz 的二次封装
  • +
+

buession-dao

+
    +
  • 对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装
  • +
  • 从代码层面上支持数据库一主多从实现读写分离,insert、update、delete 操作主库,select 操作从库
  • +
+

buession-geoip

+
    +
  • 对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息
  • +
+

buession-httpclient

+
    +
  • 对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节
  • +
  • 屏蔽了对 post form、post json 等等的技术细节
  • +
+

buession-io

+
    +
  • 封装了对文件的操作
  • +
+

buession-jdbc

+
    +
  • JDBC 通用 POJO 类定义
  • +
  • 对 Hikari、Dbcp2、Druid 等配置和数据源的封装
  • +
+

buession-json

+
    +
  • 主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现
  • +
+

buession-lang

+
    +
  • 常用枚举(如:状态-Status、性别-Gender 等)的定义
  • +
  • 常用 POJO 类(如:地理位置-Geo、Key Value-KeyValue 等)的定义
  • +
+

buession-net

+
    +
  • 网络相关工具类
  • +
+

buession-redis

+
    +
  • Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 命令保持一致
  • +
  • 对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象
  • +
+

buession-session

+
    +
  • ...
  • +
+

buession-thesaurus

+
    +
  • 对词库的解析,目前仅支持搜狗词条
  • +
+

buession-velocity

+
    +
  • spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity
  • +
+

buession-web

+
    +
  • web 相关的功能封装,支持 servlet 和 reactive
  • +
  • 封装了一些常用注解,简化了业务层方面的代码实现
  • +
  • 封装了一些常用 filter
  • +
+
\ No newline at end of file diff --git a/docs/plan.html b/docs/plan.html new file mode 100644 index 0000000..10eac48 --- /dev/null +++ b/docs/plan.html @@ -0,0 +1,24 @@ +下一步计划-文档

下一步计划

+
    +
  1. 下一步计划,在 2.0.x 版本中,完善代码中的注释,便于生成的 javadoc 利于查阅,同步完善官网文档
  2. +
  3. 同时,对现有代码中存在的 BUG 进行修复,和代码层面的优化
  4. +
  5. 三方类库的小版本升级,在保证兼容性的前提下,仅大可能的通过升级三方依赖包的版本来修复其已知安全漏洞
  6. +
  7. 在 2.0.x 不会增加新的功能特性
  8. +
+

整个,大致时间计划,在2022国庆前后,终止 2.0.x 版本的开发。

+
\ No newline at end of file diff --git a/docs/quickstart.html b/docs/quickstart.html new file mode 100644 index 0000000..42c73e8 --- /dev/null +++ b/docs/quickstart.html @@ -0,0 +1,33 @@ +快速入门-文档

快速入门

+
+

TIP

+
+

官方指南假设您已了解"JAVA"方面的相关知识。

+
+
+

Buession Framework 它是日常工作中常见的通用技术需求二次封装,提供了众多常用的类库、方法、注解;同时基于 springfrawork、jsckson、jedis、apache httpcomponents、okhttp3 等等众多的优秀的三方工具的标准化的、统一的类库的上层封装,简化框架切换带来的成本。更多介绍开源查阅框架介绍

+

Buession Framework 还在引用三方类库时,确保了版本的一致性,避免在不用三方类库引用的同一个三方类库版本不一致的情况。

+

您可以根据本文档中的示例,快速熟悉 Buession Framework 的使用方法。

+

下一步可做什么?

+

您对 Buession Framework 大致了解后,您接下来可以做以下事情:

+
    +
  • 了解兼容性:了解 Buession Framework 的兼容性
  • +
  • 安装:安装/引用 Buession Framework
  • +
  • 使用:开始使用 Buession Framework 功能
  • +
+
\ No newline at end of file diff --git a/docs/requirement.html b/docs/requirement.html new file mode 100644 index 0000000..2eb37e8 --- /dev/null +++ b/docs/requirement.html @@ -0,0 +1,40 @@ +环境要求-文档

环境要求

+

JDK

+

JDK 8+

+

构建工具

+ + + + + + + + + + + + + + + + + +
构建工具版本
Maven3.5+
Gradle6.x+,推荐 6.3 及以上版本
+

Servlet 容器

+

支持 servlet 3.1+,推荐使用 servlet 4.0 及以上版本。

+
\ No newline at end of file diff --git a/docs/version.html b/docs/version.html new file mode 100644 index 0000000..03f5007 --- /dev/null +++ b/docs/version.html @@ -0,0 +1,26 @@ +版本说明-文档

版本说明

+

该项目基于 GNU 版风格定义项目版本,即:主版本号.子版本号.修正版本号。

+

管理策略

+
    +
  • 主版本号,发生变更时,不保证所有的 API 对上一个版本兼容,但保障大部分能兼容;主版本变更,可能涉及类、接口、枚举、方法的删除,或者包名的变更
  • +
  • 子版本号,发生变更时,完全兼容上一个版本,主要会增加一些小的功能或API,底层逻辑的调整调优
  • +
  • 修正版本号,主要用于修复 BUG、优化性能、安全漏洞修复,不会新增、变更、删除已有 API
  • +
+

三方包兼容性说明

+

当引用的三方包,我们保证尽大可能兼容。但对于 springframework、springboot、springcloud、springsecurity、springdata 等 spring 家族组件,以及 servlet 兼容对应的主版本。

+
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..9fd6dc7 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ +Buession Logging

一款用于记录用户操作行为的日志框架

用于记录用户操作行为。

当前版本: v0.0.2

bg
优雅

经过精雕细琢,我们带给大家一个精心设计的、标准的、高内聚低耦合的通用类库

灵活

非重复造车轮,我们是整合市面上开源的类库,以标准的接口暴露给上层用户,用户可替换或自行封装同类组件。在此基础上,封装了大量的常用的类库。

易用

开箱即用,API 基本统一,学习成本低

统一

引入相同版本的三方库,类库 API 命名、参数、包路径等统一化、标准化

\ No newline at end of file diff --git a/js/stat.js b/js/stat.js new file mode 100644 index 0000000..7118130 --- /dev/null +++ b/js/stat.js @@ -0,0 +1,7 @@ +var _hmt = _hmt || []; +(function() { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?d9dc323750cc3d97dd18565629fd5119"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); +})(); \ No newline at end of file diff --git a/manual/2.0/aop/index.html b/manual/2.0/aop/index.html new file mode 100644 index 0000000..da61e08 --- /dev/null +++ b/manual/2.0/aop/index.html @@ -0,0 +1,27 @@ +buession-aop 参考手册-参考手册

buession-aop 参考手册

+

AOP 封装,方便实现自定义注解

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-aop</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/beans/index.html b/manual/2.0/beans/index.html new file mode 100644 index 0000000..6870910 --- /dev/null +++ b/manual/2.0/beans/index.html @@ -0,0 +1,93 @@ +buession-beans 参考手册-参考手册

buession-beans 参考手册

+

该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-beans</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

属性拷贝

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.copyProperties(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.copyProperties(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

属性映射

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.populate(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.populate(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

Bean 转换为 Map

+

使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Key

+
import com.buession.beans.BeanUtils;
+
+Map<String, Object> result = BeanUtils.toMap(bean)
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/builder.html b/manual/2.0/core/builder.html new file mode 100644 index 0000000..bb922c3 --- /dev/null +++ b/manual/2.0/core/builder.html @@ -0,0 +1,140 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

Map、集合的便捷式构建,减少您的代码行数。

+

您需要往 Map、List 中添加元素的传统写法是:

+
import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+List<String> list = new ArrayList<>();
+list.add("A");
+list.add("B");
+list.add("C");
+
+Map<String, Object> map = new HashMap<>();
+map.put("a", "A");
+map.put("b", "B");
+map.put("c", "C");
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.Map;
+
+List<String> list = ListBuilder.<String>create().add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create().put("a", "A").put("b", "B").put("c", "C");
+
+

此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+List<String> list = ListBuilder.<String>create(LinkedList.class).add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create(LinkedHashMap.class).put("a", "A").put("b", "B").put("c", "C");
+
+
    +
  • 注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数
  • +
+

当您有 value 为 null 时,不添加到 List 时,传统写法:

+
import java.util.ArrayList;
+import java.util.List;
+
+String value = null;
+List<String> list = new ArrayList<>();
+
+if(value != null){
+	list.add(value);
+}
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+String value = null;
+List<String> list = ListBuilder.<String>create().addIfPresent(value).build();
+
+

Map、Set、Queue 同理。

+

便捷方法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
List ListBuilder.epmty()创建空的 V 类型的 List 对象
List ListBuilder.of()创建空的 V 类型的 List 对象
List ListBuilder.of(V value)创建仅有一个元素的 V 类型的 List 对象
Queue QueueBuilder.epmty()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of(V value)创建仅有一个元素的 V 类型的 Queue 对象
Set SetBuilder.epmty()创建空的 V 类型的 Set 对象
Set SetBuilder.of()创建空的 V 类型的 Set 对象
Set SetBuilder.of(V value)创建仅有一个元素的 V 类型的 Set 对象
<K, V> Map<K, V> MapBuilder.epmty()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of(V value)创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象
+

empty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/codec.html b/manual/2.0/core/codec.html new file mode 100644 index 0000000..517aaf5 --- /dev/null +++ b/manual/2.0/core/codec.html @@ -0,0 +1,82 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。

+

我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。

+

此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。

+

此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。

+
USER_NOT_FOUND.code = 10404
+USER_NOT_FOUND.message = 用户不存在
+
+USER_LOGIN_FAILURE.code = 10405
+USER_LOGIN_FAILURE.message = 登录失败
+
+
<context:property-placeholder location="classpath:error_message.properties"/>
+
+<util:properties location="classpath:error_message.properties" local-override="true"/>
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message("USER_NOT_FOUND")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。

+
USER_NOT_FOUND.errorCode = 10404
+USER_NOT_FOUND.errorMessage = 用户不存在
+
+USER_LOGIN_FAILURE.errorCode = 10405
+USER_LOGIN_FAILURE.errorMessage = 登录失败
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message(value = "USER_NOT_FOUND", code = "errorCode", message = "errorMessage")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/collect.html b/manual/2.0/core/collect.html new file mode 100644 index 0000000..d6772d3 --- /dev/null +++ b/manual/2.0/core/collect.html @@ -0,0 +1,160 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

收集器

+

数组、Map、集合的工具类

+

数组

+

数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 ListSet 以及字符串类型的数组、数组合并、数组元素操作等方法。

+

检测数组 array 中是否存在元素 element:

+
import com.buession.core.collect.Arrays;
+
+boolean result = Arrays.contains(array, element);
+
+

返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.indexOf(array, element);
+
+

返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.lastIndexOf(array, element);
+
+

将字符串拼接会字符串:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String result = Arrays.toString(array);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String glue = "-";
+String result = Arrays.toString(array, glue);
+// 1-2-3
+
+

可以通过方法 toList、toSet 转换为 List 和 Set:

+
import com.buession.core.collect.Arrays;
+import java.util.List;
+import java.util.Set;
+
+int[] array = {1, 2, 3};
+List<Integer> list = Arrays.toList(array);
+Set<Integer> set = Arrays.toSet(array);
+
+

将数组转换为字符串类型的数组:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String[] result = Arrays.toStringArray(array);
+
+

将数组进行合并:

+
import com.buession.core.collect.Arrays;
+
+String[] result = Arrays.toStringArray(array1, array2, array3);
+
+

对数组元素进行操作:

+
import com.buession.core.collect.Arrays;
+
+String[] array = {"A", "B", "C"};
+String[] result = Arrays.map(array, String.class, fn);
+
+

第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现

+

Lists

+

List 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Lists.toString(list);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Lists.toString(list);
+// 1-2-3
+
+

可以通过方法 toSet 将 List 转换为 Set:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+Set<Integer> set = Lists.toSet(list);
+
+

Sets

+

Sett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Sets.toString(set);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Sets.toString(list);
+// 1-2-3
+
+

可以通过方法 toList 将 Set 转换为 List:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+List<Integer> list = Sets.toList(set);
+
+

Maps

+

Map 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。

+

对 Map 进行操作:

+
import com.buession.core.collect.Maps;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> maps = new HashMap<>();
+Map<String, String> result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());
+
+

第二个、第三参数为 java.util.function.Function 的实现

+

可以通过方法 toList 将 Map 的 value 转换为 List:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+List<T> list = Maps.toList(maps);
+
+

可以通过方法 toSet 将 Map 的 value 转换为 Set:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.Set;
+
+Set<T> set = Maps.toSet(maps);
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/context.html b/manual/2.0/core/context.html new file mode 100644 index 0000000..1696e7f --- /dev/null +++ b/manual/2.0/core/context.html @@ -0,0 +1,91 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

上下文

+

注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。

+

在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:

+
    +
  • 逻辑少
  • +
  • 与 Dao 层进行交互,多个 Dao 层的复用
  • +
  • Service 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层
  • +
+
import com.buession.core.context.stereotype.Manager;
+import org.springframework.stereotype.Service;
+
+public interface UserManager {
+
+	User getByPrimary(int id);
+
+}
+
+@Manager
+public class UserManagerImpl implements UserManager {
+
+	@Autowired
+	private UserDao userDao;
+
+	@Autowired
+	private UserProfileDao userProfileDao;
+
+	@Autowired
+	private RedisTemplate redisTemplate;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = redisTemplate.hGetObject("user", Integer.toString(id), User.class);
+
+		if(user == null){
+			user = userDao.getByPrimary(id);
+			if(user != null){
+				user.setProfile(userProfileDao.getByUserId(id));
+				redisTemplate.hSet("user", Integer.toString(id), user);
+			}else{
+				throw new RuntimeException("用户不存在");
+			}
+		}
+
+		return user;
+	}
+
+}
+
+public interface UserService {
+
+	User getByPrimary(int id);
+
+}
+
+@Service
+public class UserServiceImpl implements UserService {
+
+	@Autowired
+	private UserManager userManager;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = userManager.getByPrimary(id);
+
+		...
+
+		return user;
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/converter.html b/manual/2.0/core/converter.html new file mode 100644 index 0000000..ce5a347 --- /dev/null +++ b/manual/2.0/core/converter.html @@ -0,0 +1,155 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

接口定义:

+
@FunctionalInterface
+public interface Converter<S, T> {
+
+	T convert(final S source);
+
+}
+
+

将类似为 S 的对象转换为类型为 T 的对象。

+

内置转换器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
转换器说明
ArrayConverter<S, T>将 S 类型的数组转换为 T 类型的数组
EnumConverter<E extends Enum>枚举转换器,将字符串转换为枚举 E
BinaryEnumConverter<E extends Enum>枚举转换器,将 byte 数组转换为枚举 E
BooleanStatusConverter将布尔值转换为 com.buession.lang.Status
StatusBooleanConvertercom.buession.lang.Status 转换为布尔值
PredicateStatusConverter通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status
ListConverter<S, T>将 S 类型的 List 对象转换为 T 类型的 List 对象
SetConverter<S, T>将 S 类型的 Set 对象转换为 T 类型的 Set 对象
MapConverter<SK, SV, TK, TV>将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map
+

将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象

+
import com.buession.core.converter.MapConverter;
+import java.util.Map;
+
+Map<Integer, Object> source;
+Map<String, String> target;
+MapConverter<Integer, Object, String, String> converter = new MapConverter<>();
+
+target = converter.convert(source);
+
+

将字符串转换为枚举

+
import com.buession.core.converter.EnumConverter;
+import com.buession.lang.Gender;
+
+Gender target;
+EnumConverter<Gender> converter = new EnumConverter<>(Gender.class);
+
+target = converter.convert("FEMALE");
+// Gender.FEMALE
+
+target = converter.convert("F");
+// null
+
+

POJO 类映射

+

我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。

+
public interface Mapper<S, T> {
+
+	/**
+	 * 将源对象映射到目标对象
+	 *
+	 * @param object
+	 * 		源对象
+	 *
+	 * @return 目标对象实例
+	 */
+	T mapping(S object);
+
+	/**
+	 * 将源对象数组映射到目标对象数组
+	 *
+	 * @param object
+	 * 		源对象数组
+	 *
+	 * @return 目标对象实例数组
+	 */
+	T[] mapping(S[] object);
+
+	/**
+	 * 将源 list 对象映射到目标 list 对象
+	 *
+	 * @param object
+	 * 		源 list 对象
+	 *
+	 * @return 目标对象 list 实例
+	 */
+	List<T> mapping(List<S> object);
+
+	/**
+	 * 将源 set 对象映射到目标 set 对象
+	 *
+	 * @param object
+	 * 		源 set 对象
+	 *
+	 * @return 目标对象 set 实例
+	 */
+	Set<T> mapping(Set<S> object);
+
+}
+
+

我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样。

+
import com.buession.core.converter.mapper.PropertyMapper;
+
+User source = new User();
+User target = new User();
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
+propertyMapper.form(source::getId).to(target:setId)
+// null
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/datetime.html b/manual/2.0/core/datetime.html new file mode 100644 index 0000000..1da5430 --- /dev/null +++ b/manual/2.0/core/datetime.html @@ -0,0 +1,33 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

日期时间

+

日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。

+

获取当前 Unix 时间戳(秒):

+
import com.buession.core.datetime.DateTime;
+
+DateTime.unixtime();
+
+

该方法我们在实际业务中经常用到。

+

以 "msec sec" 的格式返回当前 Unix 时间戳和微秒数:

+
import com.buession.core.datetime.DateTime;
+
+DateTime.microtime();
+// 1657171717 948000
+
+

该方法参考 PHP 的 microtime 函数而来。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/exception.html b/manual/2.0/core/exception.html new file mode 100644 index 0000000..6bfcd28 --- /dev/null +++ b/manual/2.0/core/exception.html @@ -0,0 +1,70 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

异常

+

通用异常的定义。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
异常说明
AccessException拒绝访问异常
ClassInstantiationException类实例化异常
ConversionException数据类型转换异常
DataAlreadyExistException数据已存在异常
DataNotFoundException数据不存在或未找到异常
InsteadException类方法废弃后,需要使用其它类库方法来替代
NestedRuntimeException嵌套运行时异常
OperationException运算异常
PresentException--
SerializationException序列化异常
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/id.html b/manual/2.0/core/id.html new file mode 100644 index 0000000..a1f5ca1 --- /dev/null +++ b/manual/2.0/core/id.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。

+

接口规范。

+
public interface IdGenerator<T> {
+
+	/**
+	 * 获取下一个 ID
+	 *
+	 * @return ID
+	 */
+	T nextId();
+
+}
+
+

ID 生成器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
生成器说明
AtomicSimpleIdGenerator基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
AtomicUUIDIdGenerator基于 AtomicLong 递增的,UUID ID 生成器
NanoIDIdGeneratorjnanoid ID 生成器,可通过构造函数指定字符范围、长度
RandomDigitIdGenerator随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定
RandomIdGenerator随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位
SimpleIdGenerator简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
SnowflakeIdGenerator雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值
UUIDIdGeneratorUUID ID 生成器
+
import com.buession.core.id.AtomicUUIDIdGenerator;
+import com.buession.core.id.NanoIDIdGenerator;
+import com.buession.core.id.SnowflakeIdGenerator;
+import com.buession.core.id.UUIDIdGenerator;
+import com.buession.core.id.SimpleIdGenerator;
+
+AtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000001
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000002
+
+NanoIDIdGenerator idGenerator = new NanoIDIdGenerator();
+idGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK
+
+SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();
+idGenerator.nextId(); // 170602258864545792
+
+UUIDIdGenerator idGenerator = new UUIDIdGenerator();
+idGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4
+
+SimpleIdGenerator idGenerator = new SimpleIdGenerator();
+idGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05
+
+
    +
  • 注:关于雪花算法、jnanoid 算法的可自行搜索。
  • +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/index.html b/manual/2.0/core/index.html new file mode 100644 index 0000000..c8d6fc4 --- /dev/null +++ b/manual/2.0/core/index.html @@ -0,0 +1,53 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-core</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

构建器

+

Map、集合的便捷式构建,减少您的代码行数

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中

+

收集器

+

数组、Map、集合的工具类

+

上下文

+

定义应用上下文的类库、注解

+

转换器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

日期时间

+

日期、时间工具

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

数学函数

+

定义了实用的数学函数

+

序列化和反序列化

+

对象的序列化和反序列化,包括二进制和 JSON。

+

验证器

+

数据验证器及其注解

+

工具类

+

常用通用性工具类

+

其它

+

通用的接口定义,框架自身类

+

异常

+

通用异常的定义

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/math.html b/manual/2.0/core/math.html new file mode 100644 index 0000000..c8d456c --- /dev/null +++ b/manual/2.0/core/math.html @@ -0,0 +1,67 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

数学函数

+

定义了实用的数学函数。

+ + + + + + + + + + + + + + + + + +
方法说明
continuousSum计算两个数之间连续相加之和
rangeValue获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身
+
import com.buession.core.math.Math;
+
+long result = Math.continuousSum(1, 100);
+// 5050
+
+
import com.buession.core.math.Math;
+
+long value = 3;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 4
+
+
import com.buession.core.math.Math;
+
+long value = 5;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 5
+
+
import com.buession.core.math.Math;
+
+long value = 11;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 10
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/other.html b/manual/2.0/core/other.html new file mode 100644 index 0000000..236fa9b --- /dev/null +++ b/manual/2.0/core/other.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

其它

+

通用的接口定义,框架自身类,以及其它杂项。

+

框架自身工具

+

获取 Buession Framework 版本:

+
import com.buession.core.Framework;
+import com.buession.core.BuesssionFrameworkVersion;
+
+BuesssionFrameworkVersion.getVersion(); // 2.0.0
+Framework.VERSION; // 2.0.0
+
+

获取 Buession Framework 框架名称:

+
import com.buession.core.Framework;
+
+Framework.NAME; // "Buession"
+
+

命令执行器

+

命令执行器接口:

+
/**
+ * 命令执行器
+ *
+ * @param <C>
+ * 		命令上下文
+ * @param <R>
+ * 		命令执行返回值
+ */
+@FunctionalInterface
+public interface Executor<C, R> {
+
+	/**
+	 * 命令执行
+	 *
+	 * @param context
+	 * 		命令执行器上下文
+	 *
+	 * @return 命令执行返回值,R 类型的实例
+	 */
+	R execute(C context);
+
+}
+
+

您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。

+

销毁接口

+

功能类似 java.io.Closeable

+
public interface Destroyable {
+
+	/**
+	 * 销毁相关资源
+	 *
+	 * @throws IOException
+	 * 		IO 错误时抛出
+	 */
+	void destroy() throws IOException;
+
+}
+
+

Rawable

+

原始的,约定实现该接口的类,必须返回原始字节数组。

+
public interface Rawable {
+
+	/**
+	 * 返回原始的字节数组
+	 *
+	 * @return 原始的字节数组
+	 */
+	byte[] getRaw();
+
+}
+
+

名称节点

+

名称节点,约定实现该接口的类应该返回一个名称

+
public interface NamedNode {
+
+	/**
+	 * 返回节点名称
+	 *
+	 * @return 节点名称
+	 */
+	@Nullable
+	String getName();
+
+}
+
+

分页

+

com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。

+
\ No newline at end of file diff --git a/manual/2.0/core/serializer.html b/manual/2.0/core/serializer.html new file mode 100644 index 0000000..af6a016 --- /dev/null +++ b/manual/2.0/core/serializer.html @@ -0,0 +1,56 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

对象的序列化和反序列化,包括二进制和 JSON。

+

您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。

+

序列化、反序列化类

+ + + + + + + + + + + + + + + + + + + + + + + + + +
说明
DefaultByteArraySerializer将对象序列化为二进制,或将二进制反序列化为对象
FastJsonJsonSerializer基于 FastJSON 的对象与 JSON 之间的序列化和反序列化
GsonJsonSerializer基于 Gson 的对象与 JSON 之间的序列化和反序列化
JacksonJsonSerializer基于 Jackson2 的对象与 JSON 之间的序列化和反序列化
+
    +
  1. 通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象
  2. +
  3. DefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象
  4. +
  5. DefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化
  6. +
  7. 在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑
  8. +
  9. FastJsonJsonSerializerGsonJsonSerializerJacksonJsonSerializer 可以通过参数 Class<T>TypeReference<V> 指定返回的对象类型
  10. +
  11. com.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回
  12. +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/utils.html b/manual/2.0/core/utils.html new file mode 100644 index 0000000..6ee87f8 --- /dev/null +++ b/manual/2.0/core/utils.html @@ -0,0 +1,199 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

工具类

+

常用通用性工具类。

+

Byte 数组比较

+

ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。

+

注解工具

+

AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class<?> clazz, final Class<? extends Annotation>[] annotations)hasMethodAnnotationPresent(Method method, final Class<? extends Annotation>[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。

+

断言

+

Assert 和 springframework 中的注解类似,不过逻辑有些相反。

+

Byte 工具

+

ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] bytes;
+char c = ByteUtils.toChar(bytes);
+
+char[] chars = ByteUtils.toChar(bytes);
+
+

byte 数组连接。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] dest;
+byte[] source
+byte[] result = ByteUtils.concat(dest, source);
+
+

Character 工具

+

CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。

+
import com.buession.core.utils.CharacterUtils;
+
+char c;
+byte[] bytes = ByteUtils.toBytes(c);
+
+char[] chars;
+byte[] bytes = ByteUtils.toBytes(chars);
+
+

数字工具

+

NumberUtils 提供了对数字相关的操作。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
int2bytes将 int 转换为 byte[]
bytes2int将 byte[] 转换为 int
long2bytes将 long 转换为 byte[]
bytes2long将 byte[] 转换为 long
float2bytes将 float 转换为 byte[]
bytes2float将 byte[] 转换为 float
double2bytes将 double 转换为 byte[]
bytes2double将 byte[] 转换为 double
+

字符串工具

+

StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。

+

截取字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.substr("abcde", 1); // bcde
+String result = StringUtils.substr("abcde", 1, 2); // bc
+
+

生成随机字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.random(length);
+
+

比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equals("abcd", "abce", 3); // true
+boolean result = StringUtils.equals("abcd", "abce", 4); // false
+
+

忽略大小写比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equalsIgnoreCase("abcd", "Abce", 3); // true
+boolean result = StringUtils.equalsIgnoreCase("abcd", "aBce", 4); // false
+
+

拼音工具

+

PinyinUtils 封装了获取中文拼音、拼音首字母的方法。

+
import com.buession.core.utils.PinyinUtils;
+
+String result = PinyinUtils.getPinyin("中国"); // zhongguo
+String result = PinyinUtils.getPinYinFirstChar("中国"); // zg
+
+

随机数工具

+

RandomUtils 封装了随机数的生成。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
nextBoolean随机布尔值
nextBytes随机字节数组
nextInt生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE
nextLong生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE
nextFloat生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE
nextDouble生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE
+

Properties 工具

+

PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。

+
import com.buession.core.utils.SystemPropertyUtils;
+
+Integer result = PropertiesUtils.getInteger(properties, key);
+Boolean result = PropertiesUtils.getBoolean(properties, key);
+
+

System Property 工具

+

SystemPropertyUtils 封装了系统属性或系统环境变量的操作。

+

设置属性方法 setPropertySystem.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty

+
import com.buession.core.utils.SystemPropertyUtils;
+
+SystemPropertyUtils.setProperty("http.port", 8080);
+SystemPropertyUtils.setProperty("http.ssl.enable", false);
+
+

获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。

+
String value = System.getProperty(name);
+
+if(Validate.hasText(value) == false){
+  value = System.getenv(name);
+}
+
+

版本工具

+

VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。

+
import com.buession.core.utils.VersionUtils;
+
+VersionUtils.compare("1.0.0", "1.0.1-beta"); // -1
+VersionUtils.compare("1.0.0", "1.0.0r"); // -1
+
+

规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本

+

获取类的版本值

+
import com.buession.core.utils.VersionUtils;
+
+ByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.0.0
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/core/validator.html b/manual/2.0/core/validator.html new file mode 100644 index 0000000..1c2b0b0 --- /dev/null +++ b/manual/2.0/core/validator.html @@ -0,0 +1,239 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

验证器

+

数据验证器及其注解。

+

该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。

+

并提供对应的基于 javax.validation 的校验注解。

+

验证是否为 null

+

判断任意对象是否为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNull(obj);
+
+

验证是否不为 null

+

判断任意对象是否不为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNotNull(obj);
+
+

判断字符串是否为空白字符串

+

判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 false

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isBlank(str); // true
+
+String str = "";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\n";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\na";
+boolean result = Validate.isBlank(str); // false
+
+
    +
  • 注:isNotBlank 与之相反
  • +
+

判断是否为空

+

isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isEmpty(str); // true
+
+String str = " ";
+boolean result = Validate.isEmpty(str); // false
+
+boolean result = Validate.isEmpty(new String[]{}); // true
+
+boolean result = Validate.isEmpty(new Integer[]{1}); // false
+
+
    +
  • 注:isNotEmpty 与之相反
  • +
+

判断是否在两个数之间

+

isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3); // true
+
+boolean result = Validate.isBetween(2, 2, 3); // false
+
+

可通过参数设置是否包含边界值

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3, true); // true
+
+boolean result = Validate.isBetween(2, 2, 3, true); // true
+
+

判断是否为电话号码

+

isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isTel("028-12345678"); // true
+
+boolean result = Validate.isTel("028-02345678"); // false
+
+

判断是否为手机号码

+

isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isMobile("028-12345678"); // false
+
+boolean result = Validate.isMobile("13800138000"); // true
+
+

判断是否为邮政编码

+

isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isPostCode("043104"); // false
+
+boolean result = Validate.isPostCode("643104"); // true
+
+

判断是否为 QQ 号码

+

isQQ 可判断一个字符串是否为 QQ 号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isQQ("043104"); // false
+
+boolean result = Validate.isQQ("251329041"); // true
+
+

判断是否为身份证号码

+

isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+

可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx", true, "2000-01-01");
+
+

其它,更多方法可以参考手册

+

注解

+

javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解验证的数据类型说明
@AlnumCharSequence 的子类型,Character验证注解的元素值是否为数字
@AlphaCharSequence 的子类型,Character验证注解的元素值是否为数字
@NumericCharSequence 的子类型,Character验证注解的元素值是否为数字
@Betweenshort、int、double 等任何 Number 的子类型验证注解的元素值是否为在两个数之间
@EmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否为空
@NotEmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否不为空
@HasTextCharSequence 的子类型验证注解的元素值是否有非空字符
@IDCardCharSequence 的子类型验证注解的元素值是否有非空字符
@IpCharSequence 的子类型验证注解的元素值是否有非空字符
@IsbnCharSequence 的子类型验证注解的元素值是否有非空字符
@MimeTypeCharSequence 的子类型验证注解的元素值是否有非空字符
@MobileCharSequence 的子类型验证注解的元素值是否有非空字符
@Null任意类型验证注解的元素值是否为 null
@NotNull任意类型验证注解的元素值是否为 null
@PortInteger验证注解的元素值是否为 null
@PostCodeCharSequence 的子类型验证注解的元素值是否为 null
@QQCharSequence 的子类型验证注解的元素值是否为 null
@TelCharSequence 的子类型验证注解的元素值是否为 null
@XdigitCharSequence 的子类型验证注解的元素值是否为 null
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/cron/index.html b/manual/2.0/cron/index.html new file mode 100644 index 0000000..543a016 --- /dev/null +++ b/manual/2.0/cron/index.html @@ -0,0 +1,30 @@ +buession-cron 参考手册-参考手册

buession-cron 参考手册

+

对 quartz 的二次封装

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-cron</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。

+

由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。

+

且在今后的版本中,该模块可能会被废弃。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/dao/index.html b/manual/2.0/dao/index.html new file mode 100644 index 0000000..94403fa --- /dev/null +++ b/manual/2.0/dao/index.html @@ -0,0 +1,46 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-dao</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。

+

同时,我们在代码层面实现了数据库的读写分离。

+

我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。

+

Dao 接口

+

接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.0.2/com/buession/dao/Dao.html

+
public interface Dao<P, E> {
+}
+
+
    +
  • P:主键类型
  • +
  • E:实体类
  • +
+

分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset

+

条件为 Map<String, Object> 类型,允许为 null。

+

排序为 Map<String, com.buession.lang.Order> 类型,允许为 null。

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/dao/mongodb.html b/manual/2.0/dao/mongodb.html new file mode 100644 index 0000000..de287b9 --- /dev/null +++ b/manual/2.0/dao/mongodb.html @@ -0,0 +1,24 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplateslaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

AbstractMongoDBDaoreplace 执行的也是 insert。 +在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/dao/mybatis.html b/manual/2.0/dao/mybatis.html new file mode 100644 index 0000000..626c38a --- /dev/null +++ b/manual/2.0/dao/mybatis.html @@ -0,0 +1,170 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplateslaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

Mybatis 约定

+
    +
  1. 如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace
  2. +
+
namespace com.buession.dao.test.dao;
+
+public class UserDaoImpl extends AbstractMyBatisDao<Integer, User> {
+
+	@Override
+	protected String getStatement(){
+		return "com.buession.dao.test.dao.UserMapper";
+	}
+
+}
+
+
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.buession.dao.test.dao.UserMapper">
+</mapper>
+
+
    +
  1. Mapper 的 SQL ID 和方法名保持一致
  2. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQL ID说明返回值
insert插入数据影响的行数
batchInsert批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入每次插入影响的行数列表
replace替换数据,即:REPLACE 语句影响的行数
batchReplace批量替换数据,即:REPLACE 语句每次替换数据影响的行数列表
update更新数据更新条数
updateByPrimary根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值更新条数
getByPrimary根据主键查询数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
selectOne(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
select查询数据数据结果列表
getAll查询所有数据数据结果列表
count获取记录数记录数
deleteByPrimary根据主键删除数据影响条数
delete删除数据影响条数
clear清除数据影响条数
truncate截断数据影响条数
+
    +
  • 注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。 +以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。
  • +
+

Mybatis 类型处理器

+

MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeHandler说明
DefaultEnumTypeHandler默认 Enum 类型处理器,将值直接转换为枚举字段
IgnoreCaseEnumTypeHandler忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段
DefaultJsonTypeHandlerJSON 处理器,将 JSON 格式的字符串值和类型 <E> 进行转换
DefaultSetEnumTypeHandler默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素
IgnoreCaseSetEnumTypeHandler忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素
DefaultSetTypeHandler默认 Set 类型处理器,将值以 "," 拆分转换为 Set<String>
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/geoip/index.html b/manual/2.0/geoip/index.html new file mode 100644 index 0000000..545ab78 --- /dev/null +++ b/manual/2.0/geoip/index.html @@ -0,0 +1,79 @@ +buession-geoip 参考手册-参考手册

buession-geoip 参考手册

+

对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-geoip</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。

+

geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2001:0DB8:0000:0023:0008:0800:200C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。

+

获取国家信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Country country = resolver.country("114.114.114.114");
+// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}
+
+Country country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8
+// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}
+
+

获取城市信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+District district = resolver.district("114.114.114.114");
+// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}
+
+District district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8
+// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}
+
+

获取位置信息

+

位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Location location = resolver.location("114.114.114.114");
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.0617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}
+
+Location location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=200}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32400000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}
+
+

缓存

+

为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。

+

Resolver 的 Spring Factory Bean

+

我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。

+
<bean id="geoIPResolver" class="com.buession.geoip.spring.GeoIPResolverFactoryBean"
+  p:dbPath="/data/maxmind/City.mmdb"
+  p:stream-ref="dbStream"
+  p:enableCache="true/false"
+ />
+
+
    +
  1. dbPathstream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。
  2. +
  3. enableCache 可以控制是否缓存。
  4. +
+

关于 IP 库

+

buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/httpclient/configuration.html b/manual/2.0/httpclient/configuration.html new file mode 100644 index 0000000..5ce4e29 --- /dev/null +++ b/manual/2.0/httpclient/configuration.html @@ -0,0 +1,139 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接配置

+

您可以通过连接配置类 Configuration 配置 apache httpcomponentsokhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponentsokhttp3 的配置信息。

+

配置属性说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名称数据类型apache httpcomponents 对应配置okhttp3 对应配置默认值说明
maxConnectionsintmaxTotalmaxIdleConnections5000最大连接数
maxPerRouteintdefaultMaxPerRoute--500每个路由的最大连接数
idleConnectionTimeintcloseIdleConnectionskeepAliveDuration60000空闲连接存活时长(单位:毫秒)
connectTimeoutintconnectTimeoutconnectTimeout3000连接超时时间(单位:毫秒)
connectionRequestTimeoutintconnectionRequestTimeout--5000从连接池获取连接的超时时间(单位:毫秒)
readTimeoutintsocketTimeoutreadTimeout5000读取超时时间(单位:毫秒)
allowRedirectsBooleanredirectsEnabledfollowRedirects--是否允许重定向
relativeRedirectsAllowedBooleanrelativeRedirectsAllowed----是否应拒绝相对重定向
circularRedirectsAllowedBooleancircularRedirectsAllowed----是否允许循环重定向
maxRedirectsIntegermaxRedirects----最大允许重定向次数
authenticationEnabledbooleanauthenticationEnabled----是否开启 Http Basic 认证
contentCompressionEnabledbooleancontentCompressionEnabled----是否启用内容压缩
normalizeUribooleannormalizeUri----是否标准化 URI
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/httpclient/connectionmanager.html b/manual/2.0/httpclient/connectionmanager.html new file mode 100644 index 0000000..f299537 --- /dev/null +++ b/manual/2.0/httpclient/connectionmanager.html @@ -0,0 +1,23 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接管理器

+

连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponentsokhttp3 的文档。

+

您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponentsokhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。

+

关于 okhttp 连接管理器

+

okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/httpclient/index.html b/manual/2.0/httpclient/index.html new file mode 100644 index 0000000..10ea3f7 --- /dev/null +++ b/manual/2.0/httpclient/index.html @@ -0,0 +1,115 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

apache httpcomponentsokhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponentsokhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。

+

传统的方式:

+
<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import org.apache.http.HttpResponse;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.client.methods.HttpPost;
+
+HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();
+
+HttpResponse response = httpClient.execute(new HttpPost("https://www.buession.com/"));
+
+

或者

+
<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import okhttp3.HttpClientConnectionManager;
+import okhttp3.OkHttpClient;
+import okhttp3.ConnectionPool;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+
+OkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());
+HttpClient httpClient = builder.build();
+
+Builder requestBuilder = new Builder().post();
+requestBuilder.url("https://www.buession.com/");
+Request okHttpRequest = requestBuilder.build();
+
+Response httpResponse = httpClient.newCall(okHttpRequest).execute();
+
+

现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

或者

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.OkHttpHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.conn.OkHttpClientConnectionManager;
+import com.buession.httpclient.core.Response;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+
+

展望

+

目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.1) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/httpclient/method.html b/manual/2.0/httpclient/method.html new file mode 100644 index 0000000..f38cb98 --- /dev/null +++ b/manual/2.0/httpclient/method.html @@ -0,0 +1,158 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

方法

+

buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。

+

示例:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/");
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/");
+
+

您可以自定义请求头:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.List;
+import java.util.ArrayList;
+
+List<Header> headers = new ArrayList<>();
+
+headers.add(new Header("X-SDK-NAME", "Buession"));
+headers.add(new Header("X-Timestamp", System.currentTimeMillis()));
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", headers);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", headers);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", headers);
+
+

您可以设置请求参数:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> parameters = new HashMap<>();
+
+parameters.put("action", "edit");
+parameters.put("id", 1);
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", parameters);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", parameters);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", parameters);
+
+

您可以设置请求体:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import jcom.buession.httpclient.core.RequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+
+EncodedFormRequestBody requestBody = new EncodedFormRequestBody();
+
+requestBody.addRequestBodyElement("username", "buession");
+requestBody.addRequestBodyElement("password", "buession");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", requestBody);
+
+JsonRawRequestBody requestBody = new JsonRawRequestBody(new User());
+// PUT 请求
+Response response = httpClient.put("https://www.buession.com/", requestBody);
+
+

不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody

+

RequestBody

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RequestBodyContent-Type说明
InputStreamRequestBodyapplication/octet-stream二进制请求体
ChunkedInputStreamRequestBodyapplication/octet-streamChunked 二进制请求体
RepeatableInputStreamRequestBodyapplication/octet-streamRepeatable 二进制请求体
EncodedFormRequestBodyapplication/x-www-form-urlencoded普通表单请求体
MultipartFormRequestBodymultipart/form-data文件上传表单请求体
HtmlRawRequestBodytext/htmlHTML 请求体
JavaScriptRawRequestBodyapplication/javascriptJavaScript 请求体
JsonRawRequestBodyapplication/jsonJSON 请求体
TextRawRequestBodytext/plainTEXT 请求体
XmlRawRequestBodytext/xmlXML 请求体
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/httpclient/response.html b/manual/2.0/httpclient/response.html new file mode 100644 index 0000000..6734562 --- /dev/null +++ b/manual/2.0/httpclient/response.html @@ -0,0 +1,37 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

响应

+

当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。 +buession-httpclient 会将 apache httpcomponentsokhttp3 的响应对象,转换为 Response

+

需要注意的是,原生 apache httpcomponentsokhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.core.Response;
+import java.io.InputStream;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+InputStream stream = response.getInputStream(); // 以流的形式获取响应体
+String body = response.getBody(); // 以字符串的形式获取响应体
+
+stream.close();
+
+

getInputStreamgetBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponentsokhttp3 返回的流。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/index.html b/manual/2.0/index.html new file mode 100644 index 0000000..a28055c --- /dev/null +++ b/manual/2.0/index.html @@ -0,0 +1,114 @@ +API 参考手册-参考手册

API 参考手册

+

Buession Framework API 包含以下目录:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
模块使用帮助手册
buession-aop使用帮助API 手册
buession-beans使用帮助API 手册
buession-core使用帮助API 手册
buession-cron使用帮助API 手册
buession-dao使用帮助API 手册
buession-geoip使用帮助API 手册
buession-httpclient使用帮助API 手册
buession-io使用帮助API 手册
buession-jdbc使用帮助API 手册
buession-json使用帮助API 手册
buession-lang使用帮助API 手册
buession-net使用帮助API 手册
buession-redis使用帮助API 手册
buession-session使用帮助API 手册
buession-thesaurus使用帮助API 手册
buession-velocity使用帮助API 手册
buession-web使用帮助API 手册
+
\ No newline at end of file diff --git a/manual/2.0/io/index.html b/manual/2.0/io/index.html new file mode 100644 index 0000000..658cdd7 --- /dev/null +++ b/manual/2.0/io/index.html @@ -0,0 +1,86 @@ +buession-io 参考手册-参考手册

buession-io 参考手册

+

封装了对文件的操作

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-io</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块二次封装了 java java.io.Filejava.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。

+

读取文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+byte[] result = file.read();
+
+

写文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+file.write("Buession");
+file.write("Buession".getBytes());
+file.write("Buession", true); // 追加写
+
+

获取文件 MD5、SHA-1值

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+String md5 = file.getMd5(); // 获取文件 MD5
+String sha1 = file.getSha1(); // 获取文件 SHA-1
+
+

获取文件 MD5、SHA-1 值

+
import com.buession.io.file.File;
+import com.buession.io.MimeType;
+
+File file = new File("/tmp/debug.txt");
+
+MimeType result = file.getMimeType();
+
+

设置文件权限

+
import com.buession.io.file.Files;
+
+Files.chmod("/tmp/debug.txt", 0777);
+
+

设置文件用户组

+
import com.buession.io.file.Files;
+
+Files.chgrp("/tmp/debug.txt", "root");
+
+

设置文件用户

+
import com.buession.io.file.Files;
+
+Files.chown("/tmp/debug.txt", "root");
+
+

注解

+

注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。

+
import com.buession.io.json.annotation.MimeTypeString;
+
+class File {
+
+    @MimeTypeString
+    private MimeType mime;
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/jdbc/index.html b/manual/2.0/jdbc/index.html new file mode 100644 index 0000000..63b69b7 --- /dev/null +++ b/manual/2.0/jdbc/index.html @@ -0,0 +1,28 @@ +buession-jdbc 参考手册-参考手册

buession-jdbc 参考手册

+

JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-jdbc</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通过提供的 API,您可以简化对 DBCP2DruidHikariTomcat 数据源的初始化,该类库基本不单独使用。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/json/index.html b/manual/2.0/json/index.html new file mode 100644 index 0000000..577958a --- /dev/null +++ b/manual/2.0/json/index.html @@ -0,0 +1,63 @@ +buession-json 参考手册-参考手册

buession-json 参考手册

+

主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-json</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

封装了大量基于 jackson 的注解。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解说明
CalendarUnixTimestampjava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar
DateUnixTimestampjava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date
SqlDateUnixTimestampjava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date
TimestampUnixTimestampjava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp
JsonEnum2Map枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举
Sensitive通过该注解可以实现数据的脱敏
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/lang/index.html b/manual/2.0/lang/index.html new file mode 100644 index 0000000..703d364 --- /dev/null +++ b/manual/2.0/lang/index.html @@ -0,0 +1,27 @@ +buession-lang 参考手册-参考手册

buession-lang 参考手册

+

常用 POJO 类和枚举的定义,详细查看 API 参考手册。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-lang</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/net/index.html b/manual/2.0/net/index.html new file mode 100644 index 0000000..3e652e7 --- /dev/null +++ b/manual/2.0/net/index.html @@ -0,0 +1,35 @@ +buession-net 参考手册-参考手册

buession-net 参考手册

+

网络相关工具类。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-net</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

IP 地址工具类

+

IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。

+
import com.buession.net.utils.InetAddressUtis;
+
+long result = InetAddressUtis.ip2long("127.0.0.1"); // 2130706433
+String ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1
+
+

URI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/redis/datasource.html b/manual/2.0/redis/datasource.html new file mode 100644 index 0000000..94c1114 --- /dev/null +++ b/manual/2.0/redis/datasource.html @@ -0,0 +1,185 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

数据源

+

buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。 +通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。

+

数据源 DataSource 包括三个子接口:

+
    +
  • StandaloneDataSource:单机模式数据源
  • +
  • SentinelDataSource:哨兵模式数据源
  • +
  • ClusterDataSource:集群模式数据源
  • +
+

jedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。

+

在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。

+
<bean id="redisDataSource" class="com.buession.redis.client.connection.datasource.jedis.UserMapper"
+	p:host="${redis.host}"
+	p:port="${redis.port}"
+	p:password="${redis.password}" />
+
+

测试环境 properties:

+
redis.host=127.0.0.1
+redis.port=6379
+redis.password=
+
+

生产环境 properties:

+
redis.host=192.168.100.131
+redis.port=6379
+redis.password=passwd
+
+

连接池

+

通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfigConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。

+

连接池配置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项数据类型-- 默认值说明
lifobooleanGenericObjectPoolConfig.DEFAULT_LIFO池模式,为 true 时,后进先出;为 false 时,先进先出
fairnessbooleanGenericObjectPoolConfig.DEFAULT_FAIRNESS当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制
maxWaitDurationGenericObjectPoolConfig.DEFAULT_MAX_WAIT当连接池资源用尽后,调用者获取连接时的最大等待时间
minEvictableIdleTimeDuration60000连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除
softMinEvictableIdleTimeDurationGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数
evictionPolicyClassNameStringGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME驱逐策略的类名
evictorShutdownTimeoutDurationGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT关闭驱逐线程的超时时间
numTestsPerEvictionRunint-1检测空闲对象线程每次运行时检测的空闲对象的数量
testOnCreatebooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE在创建对象时检测对象是否有效,配置 true 会降低性能
testOnBorrowbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW在从对象池获取对象时是否检测对象有效,配置 true 会降低性能
testOnReturnbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能
testWhileIdlebooleantrue在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
blockWhenExhaustedbooleanGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
jmxEnabledbooleanGenericObjectPoolConfig.DEFAULT_JMX_ENABLE是否注册 JMX
jmxNamePrefixStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIXJMX 前缀
jmxNameBaseStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE使用 base + jmxNamePrefix + i 来生成 ObjectName
maxTotalintGenericObjectPoolConfig.DEFAULT_MAX_TOTAL最大连接数
minIdleintGenericObjectPoolConfig.DEFAULT_MIN_IDLE最小空闲连接数
maxIdleintGenericObjectPoolConfig.DEFAULT_MAX_IDLE最大空闲连接数
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/redis/index.html b/manual/2.0/redis/index.html new file mode 100644 index 0000000..4441d37 --- /dev/null +++ b/manual/2.0/redis/index.html @@ -0,0 +1,51 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-redis</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

介绍

+

buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。

+
import com.buession.redis.RedisTemplate;
+import com.buession.redis.core.Options;
+import com.buession.core.serializer.type.TypeReference;
+import java.utils.Map;
+import java.utils.HashMap;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.setOptions(new Options());
+redisTemplate.afterPropertiesSet();
+
+// 将 User 对象写进 key 为 user hash 中
+redisTemplate.hSet("user", "1", new User());
+
+// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User
+User user = redisTemplate.hGetObject("user", "1", User.class);
+
+// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User
+Map<String, User> data = redisTemplate.hGetAllObject("user", "1", new TypeReference<HashMap<String, User>>{});
+
+

展望

+

目前,buession-redis 仅支持 jedis,不支持 lettuce,我们预计会在下个版本或者下下个版本(即:2.1 或者 2.2)中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/redis/method.html b/manual/2.0/redis/method.html new file mode 100644 index 0000000..eee1e7e --- /dev/null +++ b/manual/2.0/redis/method.html @@ -0,0 +1,49 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

方法

+

buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。

+
import com.buession.redis.BaseRedisTemplate;
+
+BaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 删除哈希表 key 中的一个或多个指定域
+redisTemplate.hDel("user", "1", "2", "3");
+
+// 检查给定 key 是否存在
+redisTemplate.exists("user");
+
+// 获取列表 key 中,下标为 index 的元素
+redisTemplate.lIndex("user", 1);
+
+// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾
+redisTemplate.append("key", "value 1");
+
+

BaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。

+
import com.buession.redis.RedisTemplate;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类
+User user = redisTemplate.lIndexObject("user", 1, User.class);
+
+

序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/session/index.html b/manual/2.0/session/index.html new file mode 100644 index 0000000..9dcc120 --- /dev/null +++ b/manual/2.0/session/index.html @@ -0,0 +1,28 @@ +buession-session 参考手册-参考手册

buession-session 参考手册

+

无文档

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-session</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块无实际意义,将在今后的版本中会删除掉。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/thesaurus/index.html b/manual/2.0/thesaurus/index.html new file mode 100644 index 0000000..8b65eae --- /dev/null +++ b/manual/2.0/thesaurus/index.html @@ -0,0 +1,37 @@ +buession-thesaurus 参考手册-参考手册

buession-thesaurus 参考手册

+

对词库的解析,目前仅支持搜狗词条。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-thesaurus</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。

+
import com.buession.thesaurus.SogouParser;
+import com.buession.thesaurus.Parser;
+import com.buession.thesaurus.core.Word;
+import java.util.Set;
+
+Parser parser = new SogouParser();
+
+Set<Word> words parser.parse("搜谱拼音词条文件路径");
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/velocity/index.html b/manual/2.0/velocity/index.html new file mode 100644 index 0000000..627c3a2 --- /dev/null +++ b/manual/2.0/velocity/index.html @@ -0,0 +1,28 @@ +buession-velocity 参考手册-参考手册

buession-velocity 参考手册

+

spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-velocity</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/web/annotation.html b/manual/2.0/web/annotation.html new file mode 100644 index 0000000..2c88e0c --- /dev/null +++ b/manual/2.0/web/annotation.html @@ -0,0 +1,74 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

注解

+

我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解Request / Response作用域说明
@RequestClientIprequest方法参数获取当前请求的客户端 IP 地址
@ContentTyperesponse类、方法设置响应 Content-Type
@HttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值
@DisableHttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存
@ResponseHeaderresponse类、方法设置响应头
@ResponseHeadersresponse类、方法批量设置响应头
@DocumentMetaDataresponse类、方法设置页面标题、页面编码、关键字、描述、版权等等元信息
+
\ No newline at end of file diff --git a/manual/2.0/web/filter.html b/manual/2.0/web/filter.html new file mode 100644 index 0000000..f0ce66f --- /dev/null +++ b/manual/2.0/web/filter.html @@ -0,0 +1,55 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

过滤器

+

我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。

+

servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。

+

过滤器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
过滤器说明
MobileFilter当前请求是否为移动设备
PoweredByFilterPowered By 过滤器
PrintUrlFilter打印当前请求 URL 过滤器
ResponseHeaderFilter响应头过滤器,设置响应头
ResponseHeadersFilter响应头过滤器,批量设置响应头
ServerInfoFilterServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点
+
\ No newline at end of file diff --git a/manual/2.0/web/index.html b/manual/2.0/web/index.html new file mode 100644 index 0000000..472e2c3 --- /dev/null +++ b/manual/2.0/web/index.html @@ -0,0 +1,28 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-web</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

buession-web 扩展了 spring-webmvcspring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.0/web/restful.html b/manual/2.0/web/restful.html new file mode 100644 index 0000000..ed6efcb --- /dev/null +++ b/manual/2.0/web/restful.html @@ -0,0 +1,47 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

RESTFUL

+

Restful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。

+

我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。

+
@RestController
+@RequestMapping(path = "/example")
+public class ExampleController extends AbstractRestController<Integer, ExampleDto, ExampleVo> {
+
+	@Override
+	public Response<ExampleVo> add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){
+		
+	}
+
+	@Override
+	public Response<ExampleVo> edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id, @RequestBody ExampleDto example){
+
+	}
+
+	@Override
+	public Response<ExampleVo> detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+
+	}
+
+	@Override
+	public Response<ExampleVo> delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.0/web/utils.html b/manual/2.0/web/utils.html new file mode 100644 index 0000000..f796297 --- /dev/null +++ b/manual/2.0/web/utils.html @@ -0,0 +1,35 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

工具

+

我们封装了一些 web 相关的工具类,用于处理 request、response。

+

servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。

+

获取客户端真实 IP 地址:

+
RequestUtils.getClientIp(request);
+
+

我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。

+

优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr

+

是否是 Ajax 请求:

+
RequestUtils.isAjaxRequest(request);
+
+

是否是移动设备请求:

+
RequestUtils.isMobile(request);
+
+

设置缓存:

+
ResponseUtils.httpCache(response, 5); // 缓存 5 秒
+ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点
+
+
\ No newline at end of file diff --git a/manual/2.1/aop/index.html b/manual/2.1/aop/index.html new file mode 100644 index 0000000..cc7203c --- /dev/null +++ b/manual/2.1/aop/index.html @@ -0,0 +1,27 @@ +buession-aop 参考手册-参考手册

buession-aop 参考手册

+

AOP 封装,方便实现自定义注解

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-aop</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/beans/index.html b/manual/2.1/beans/index.html new file mode 100644 index 0000000..956d598 --- /dev/null +++ b/manual/2.1/beans/index.html @@ -0,0 +1,93 @@ +buession-beans 参考手册-参考手册

buession-beans 参考手册

+

该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-beans</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

属性拷贝

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.copyProperties(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.copyProperties(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

属性映射

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.populate(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.populate(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

Bean 转换为 Map

+

使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Key

+
import com.buession.beans.BeanUtils;
+
+Map<String, Object> result = BeanUtils.toMap(bean)
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/builder.html b/manual/2.1/core/builder.html new file mode 100644 index 0000000..a082c68 --- /dev/null +++ b/manual/2.1/core/builder.html @@ -0,0 +1,140 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

Map、集合的便捷式构建,减少您的代码行数。

+

您需要往 Map、List 中添加元素的传统写法是:

+
import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+List<String> list = new ArrayList<>();
+list.add("A");
+list.add("B");
+list.add("C");
+
+Map<String, Object> map = new HashMap<>();
+map.put("a", "A");
+map.put("b", "B");
+map.put("c", "C");
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.Map;
+
+List<String> list = ListBuilder.<String>create().add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create().put("a", "A").put("b", "B").put("c", "C");
+
+

此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+List<String> list = ListBuilder.<String>create(LinkedList.class).add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create(LinkedHashMap.class).put("a", "A").put("b", "B").put("c", "C");
+
+
    +
  • 注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数
  • +
+

当您有 value 为 null 时,不添加到 List 时,传统写法:

+
import java.util.ArrayList;
+import java.util.List;
+
+String value = null;
+List<String> list = new ArrayList<>();
+
+if(value != null){
+	list.add(value);
+}
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+String value = null;
+List<String> list = ListBuilder.<String>create().addIfPresent(value).build();
+
+

Map、Set、Queue 同理。

+

便捷方法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
List ListBuilder.epmty()创建空的 V 类型的 List 对象
List ListBuilder.of()创建空的 V 类型的 List 对象
List ListBuilder.of(V value)创建仅有一个元素的 V 类型的 List 对象
Queue QueueBuilder.epmty()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of(V value)创建仅有一个元素的 V 类型的 Queue 对象
Set SetBuilder.epmty()创建空的 V 类型的 Set 对象
Set SetBuilder.of()创建空的 V 类型的 Set 对象
Set SetBuilder.of(V value)创建仅有一个元素的 V 类型的 Set 对象
<K, V> Map<K, V> MapBuilder.epmty()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of(V value)创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象
+

empty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/codec.html b/manual/2.1/core/codec.html new file mode 100644 index 0000000..7718d0a --- /dev/null +++ b/manual/2.1/core/codec.html @@ -0,0 +1,82 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。

+

我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。

+

此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。

+

此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。

+
USER_NOT_FOUND.code = 10404
+USER_NOT_FOUND.message = 用户不存在
+
+USER_LOGIN_FAILURE.code = 10405
+USER_LOGIN_FAILURE.message = 登录失败
+
+
<context:property-placeholder location="classpath:error_message.properties"/>
+
+<util:properties location="classpath:error_message.properties" local-override="true"/>
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message("USER_NOT_FOUND")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。

+
USER_NOT_FOUND.errorCode = 10404
+USER_NOT_FOUND.errorMessage = 用户不存在
+
+USER_LOGIN_FAILURE.errorCode = 10405
+USER_LOGIN_FAILURE.errorMessage = 登录失败
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message(value = "USER_NOT_FOUND", code = "errorCode", message = "errorMessage")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/collect.html b/manual/2.1/core/collect.html new file mode 100644 index 0000000..3c0e096 --- /dev/null +++ b/manual/2.1/core/collect.html @@ -0,0 +1,160 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

收集器

+

数组、Map、集合的工具类

+

数组

+

数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 ListSet 以及字符串类型的数组、数组合并、数组元素操作等方法。

+

检测数组 array 中是否存在元素 element:

+
import com.buession.core.collect.Arrays;
+
+boolean result = Arrays.contains(array, element);
+
+

返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.indexOf(array, element);
+
+

返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.lastIndexOf(array, element);
+
+

将字符串拼接会字符串:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String result = Arrays.toString(array);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String glue = "-";
+String result = Arrays.toString(array, glue);
+// 1-2-3
+
+

可以通过方法 toList、toSet 转换为 List 和 Set:

+
import com.buession.core.collect.Arrays;
+import java.util.List;
+import java.util.Set;
+
+int[] array = {1, 2, 3};
+List<Integer> list = Arrays.toList(array);
+Set<Integer> set = Arrays.toSet(array);
+
+

将数组转换为字符串类型的数组:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String[] result = Arrays.toStringArray(array);
+
+

将数组进行合并:

+
import com.buession.core.collect.Arrays;
+
+String[] result = Arrays.toStringArray(array1, array2, array3);
+
+

对数组元素进行操作:

+
import com.buession.core.collect.Arrays;
+
+String[] array = {"A", "B", "C"};
+String[] result = Arrays.map(array, String.class, fn);
+
+

第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现

+

Lists

+

List 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Lists.toString(list);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Lists.toString(list);
+// 1-2-3
+
+

可以通过方法 toSet 将 List 转换为 Set:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+Set<Integer> set = Lists.toSet(list);
+
+

Sets

+

Sett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Sets.toString(set);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Sets.toString(list);
+// 1-2-3
+
+

可以通过方法 toList 将 Set 转换为 List:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+List<Integer> list = Sets.toList(set);
+
+

Maps

+

Map 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。

+

对 Map 进行操作:

+
import com.buession.core.collect.Maps;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> maps = new HashMap<>();
+Map<String, String> result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());
+
+

第二个、第三参数为 java.util.function.Function 的实现

+

可以通过方法 toList 将 Map 的 value 转换为 List:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+List<T> list = Maps.toList(maps);
+
+

可以通过方法 toSet 将 Map 的 value 转换为 Set:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.Set;
+
+Set<T> set = Maps.toSet(maps);
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/context.html b/manual/2.1/core/context.html new file mode 100644 index 0000000..b32cb58 --- /dev/null +++ b/manual/2.1/core/context.html @@ -0,0 +1,91 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

上下文

+

注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。

+

在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:

+
    +
  • 逻辑少
  • +
  • 与 Dao 层进行交互,多个 Dao 层的复用
  • +
  • Service 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层
  • +
+
import com.buession.core.context.stereotype.Manager;
+import org.springframework.stereotype.Service;
+
+public interface UserManager {
+
+	User getByPrimary(int id);
+
+}
+
+@Manager
+public class UserManagerImpl implements UserManager {
+
+	@Autowired
+	private UserDao userDao;
+
+	@Autowired
+	private UserProfileDao userProfileDao;
+
+	@Autowired
+	private RedisTemplate redisTemplate;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = redisTemplate.hGetObject("user", Integer.toString(id), User.class);
+
+		if(user == null){
+			user = userDao.getByPrimary(id);
+			if(user != null){
+				user.setProfile(userProfileDao.getByUserId(id));
+				redisTemplate.hSet("user", Integer.toString(id), user);
+			}else{
+				throw new RuntimeException("用户不存在");
+			}
+		}
+
+		return user;
+	}
+
+}
+
+public interface UserService {
+
+	User getByPrimary(int id);
+
+}
+
+@Service
+public class UserServiceImpl implements UserService {
+
+	@Autowired
+	private UserManager userManager;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = userManager.getByPrimary(id);
+
+		...
+
+		return user;
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/converter.html b/manual/2.1/core/converter.html new file mode 100644 index 0000000..faa6cd0 --- /dev/null +++ b/manual/2.1/core/converter.html @@ -0,0 +1,159 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

接口定义:

+
@FunctionalInterface
+public interface Converter<S, T> {
+
+	T convert(final S source);
+
+}
+
+

将类似为 S 的对象转换为类型为 T 的对象。

+

内置转换器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
转换器说明
ArrayConverter<S, T>将 S 类型的数组转换为 T 类型的数组
EnumConverter<E extends Enum>枚举转换器,将字符串转换为枚举 E
BinaryEnumConverter<E extends Enum>枚举转换器,将 byte 数组转换为枚举 E
BooleanStatusConverter将布尔值转换为 com.buession.lang.Status
StatusBooleanConvertercom.buession.lang.Status 转换为布尔值
PredicateStatusConverter通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status
ListConverter<S, T>将 S 类型的 List 对象转换为 T 类型的 List 对象
SetConverter<S, T>将 S 类型的 Set 对象转换为 T 类型的 Set 对象
MapConverter<SK, SV, TK, TV>将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map
+

将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象

+
import com.buession.core.converter.MapConverter;
+import java.util.Map;
+
+Map<Integer, Object> source;
+Map<String, String> target;
+MapConverter<Integer, Object, String, String> converter = new MapConverter<>();
+
+target = converter.convert(source);
+
+

将字符串转换为枚举

+
import com.buession.core.converter.EnumConverter;
+import com.buession.lang.Gender;
+
+Gender target;
+EnumConverter<Gender> converter = new EnumConverter<>(Gender.class);
+
+target = converter.convert("FEMALE");
+// Gender.FEMALE
+
+target = converter.convert("F");
+// null
+
+

POJO 类映射

+

我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。

+
public interface Mapper<S, T> {
+
+	/**
+	 * 将源对象映射到目标对象
+	 *
+	 * @param object
+	 * 		源对象
+	 *
+	 * @return 目标对象实例
+	 */
+	T mapping(S object);
+
+	/**
+	 * 将源对象数组映射到目标对象数组
+	 *
+	 * @param object
+	 * 		源对象数组
+	 *
+	 * @return 目标对象实例数组
+	 */
+	T[] mapping(S[] object);
+
+	/**
+	 * 将源 list 对象映射到目标 list 对象
+	 *
+	 * @param object
+	 * 		源 list 对象
+	 *
+	 * @return 目标对象 list 实例
+	 */
+	List<T> mapping(List<S> object);
+
+	/**
+	 * 将源 set 对象映射到目标 set 对象
+	 *
+	 * @param object
+	 * 		源 set 对象
+	 *
+	 * @return 目标对象 set 实例
+	 */
+	Set<T> mapping(Set<S> object);
+
+}
+
+

我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。

+
import com.buession.core.converter.mapper.PropertyMapper;
+
+User source = new User();
+User target = new User();
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
+propertyMapper.form(source::getId).to(target:setId)
+// null
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();
+propertyMapper.form(source::getUsername).to(target:setUsername)
+// null
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/datetime.html b/manual/2.1/core/datetime.html new file mode 100644 index 0000000..55a3798 --- /dev/null +++ b/manual/2.1/core/datetime.html @@ -0,0 +1,33 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

日期时间

+

日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。

+

获取当前 Unix 时间戳(秒):

+
import com.buession.core.datetime.DateTime;
+
+DateTime.unixtime();
+
+

该方法我们在实际业务中经常用到。

+

以 "msec sec" 的格式返回当前 Unix 时间戳和微秒数:

+
import com.buession.core.datetime.DateTime;
+
+DateTime.microtime();
+// 1657171717 948000
+
+

该方法参考 PHP 的 microtime 函数而来。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/exception.html b/manual/2.1/core/exception.html new file mode 100644 index 0000000..e953465 --- /dev/null +++ b/manual/2.1/core/exception.html @@ -0,0 +1,70 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

异常

+

通用异常的定义。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
异常说明
AccessException拒绝访问异常
ClassInstantiationException类实例化异常
ConversionException数据类型转换异常
DataAlreadyExistException数据已存在异常
DataNotFoundException数据不存在或未找到异常
InsteadException类方法废弃后,需要使用其它类库方法来替代
NestedRuntimeException嵌套运行时异常
OperationException运算异常
PresentException--
SerializationException序列化异常
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/id.html b/manual/2.1/core/id.html new file mode 100644 index 0000000..35983f8 --- /dev/null +++ b/manual/2.1/core/id.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。

+

接口规范。

+
public interface IdGenerator<T> {
+
+	/**
+	 * 获取下一个 ID
+	 *
+	 * @return ID
+	 */
+	T nextId();
+
+}
+
+

ID 生成器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
生成器说明
AtomicSimpleIdGenerator基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
AtomicUUIDIdGenerator基于 AtomicLong 递增的,UUID ID 生成器
NanoIDIdGeneratorjnanoid ID 生成器,可通过构造函数指定字符范围、长度
RandomDigitIdGenerator随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定
RandomIdGenerator随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位
SimpleIdGenerator简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
SnowflakeIdGenerator雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值
UUIDIdGeneratorUUID ID 生成器
+
import com.buession.core.id.AtomicUUIDIdGenerator;
+import com.buession.core.id.NanoIDIdGenerator;
+import com.buession.core.id.SnowflakeIdGenerator;
+import com.buession.core.id.UUIDIdGenerator;
+import com.buession.core.id.SimpleIdGenerator;
+
+AtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000001
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000002
+
+NanoIDIdGenerator idGenerator = new NanoIDIdGenerator();
+idGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK
+
+SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();
+idGenerator.nextId(); // 170602258864545792
+
+UUIDIdGenerator idGenerator = new UUIDIdGenerator();
+idGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4
+
+SimpleIdGenerator idGenerator = new SimpleIdGenerator();
+idGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05
+
+
    +
  • 注:关于雪花算法、jnanoid 算法的可自行搜索。
  • +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/index.html b/manual/2.1/core/index.html new file mode 100644 index 0000000..76119eb --- /dev/null +++ b/manual/2.1/core/index.html @@ -0,0 +1,53 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-core</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

构建器

+

Map、集合的便捷式构建,减少您的代码行数

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中

+

收集器

+

数组、Map、集合的工具类

+

上下文

+

定义应用上下文的类库、注解

+

转换器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

日期时间

+

日期、时间工具

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

数学函数

+

定义了实用的数学函数

+

序列化和反序列化

+

对象的序列化和反序列化,包括二进制和 JSON。

+

验证器

+

数据验证器及其注解

+

工具类

+

常用通用性工具类

+

其它

+

通用的接口定义,框架自身类

+

异常

+

通用异常的定义

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/math.html b/manual/2.1/core/math.html new file mode 100644 index 0000000..c59c565 --- /dev/null +++ b/manual/2.1/core/math.html @@ -0,0 +1,67 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

数学函数

+

定义了实用的数学函数。

+ + + + + + + + + + + + + + + + + +
方法说明
continuousSum计算两个数之间连续相加之和
rangeValue获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身
+
import com.buession.core.math.Math;
+
+long result = Math.continuousSum(1, 100);
+// 5050
+
+
import com.buession.core.math.Math;
+
+long value = 3;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 4
+
+
import com.buession.core.math.Math;
+
+long value = 5;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 5
+
+
import com.buession.core.math.Math;
+
+long value = 11;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 10
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/other.html b/manual/2.1/core/other.html new file mode 100644 index 0000000..b90b2cd --- /dev/null +++ b/manual/2.1/core/other.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

其它

+

通用的接口定义,框架自身类,以及其它杂项。

+

框架自身工具

+

获取 Buession Framework 版本:

+
import com.buession.core.Framework;
+import com.buession.core.BuesssionFrameworkVersion;
+
+BuesssionFrameworkVersion.getVersion(); // 2.1.0
+Framework.VERSION; // 2.1.0
+
+

获取 Buession Framework 框架名称:

+
import com.buession.core.Framework;
+
+Framework.NAME; // "Buession"
+
+

命令执行器

+

命令执行器接口:

+
/**
+ * 命令执行器
+ *
+ * @param <C>
+ * 		命令上下文
+ * @param <R>
+ * 		命令执行返回值
+ */
+@FunctionalInterface
+public interface Executor<C, R> {
+
+	/**
+	 * 命令执行
+	 *
+	 * @param context
+	 * 		命令执行器上下文
+	 *
+	 * @return 命令执行返回值,R 类型的实例
+	 */
+	R execute(C context);
+
+}
+
+

您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。

+

销毁接口

+

功能类似 java.io.Closeable

+
public interface Destroyable {
+
+	/**
+	 * 销毁相关资源
+	 *
+	 * @throws IOException
+	 * 		IO 错误时抛出
+	 */
+	void destroy() throws IOException;
+
+}
+
+

Rawable

+

原始的,约定实现该接口的类,必须返回原始字节数组。

+
public interface Rawable {
+
+	/**
+	 * 返回原始的字节数组
+	 *
+	 * @return 原始的字节数组
+	 */
+	byte[] getRaw();
+
+}
+
+

名称节点

+

名称节点,约定实现该接口的类应该返回一个名称

+
public interface NamedNode {
+
+	/**
+	 * 返回节点名称
+	 *
+	 * @return 节点名称
+	 */
+	@Nullable
+	String getName();
+
+}
+
+

分页

+

com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。

+
\ No newline at end of file diff --git a/manual/2.1/core/serializer.html b/manual/2.1/core/serializer.html new file mode 100644 index 0000000..9d269c4 --- /dev/null +++ b/manual/2.1/core/serializer.html @@ -0,0 +1,56 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

对象的序列化和反序列化,包括二进制和 JSON。

+

您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。

+

序列化、反序列化类

+ + + + + + + + + + + + + + + + + + + + + + + + + +
说明
DefaultByteArraySerializer将对象序列化为二进制,或将二进制反序列化为对象
FastJsonJsonSerializer基于 FastJSON 的对象与 JSON 之间的序列化和反序列化
GsonJsonSerializer基于 Gson 的对象与 JSON 之间的序列化和反序列化
JacksonJsonSerializer基于 Jackson2 的对象与 JSON 之间的序列化和反序列化
+
    +
  1. 通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象
  2. +
  3. DefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象
  4. +
  5. DefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化
  6. +
  7. 在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑
  8. +
  9. FastJsonJsonSerializerGsonJsonSerializerJacksonJsonSerializer 可以通过参数 Class<T>TypeReference<V> 指定返回的对象类型
  10. +
  11. com.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回
  12. +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/utils.html b/manual/2.1/core/utils.html new file mode 100644 index 0000000..cd49366 --- /dev/null +++ b/manual/2.1/core/utils.html @@ -0,0 +1,199 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

工具类

+

常用通用性工具类。

+

Byte 数组比较

+

ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。

+

注解工具

+

AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class<?> clazz, final Class<? extends Annotation>[] annotations)hasMethodAnnotationPresent(Method method, final Class<? extends Annotation>[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。

+

断言

+

Assert 和 springframework 中的注解类似,不过逻辑有些相反。

+

Byte 工具

+

ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] bytes;
+char c = ByteUtils.toChar(bytes);
+
+char[] chars = ByteUtils.toChar(bytes);
+
+

byte 数组连接。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] dest;
+byte[] source
+byte[] result = ByteUtils.concat(dest, source);
+
+

Character 工具

+

CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。

+
import com.buession.core.utils.CharacterUtils;
+
+char c;
+byte[] bytes = ByteUtils.toBytes(c);
+
+char[] chars;
+byte[] bytes = ByteUtils.toBytes(chars);
+
+

数字工具

+

NumberUtils 提供了对数字相关的操作。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
int2bytes将 int 转换为 byte[]
bytes2int将 byte[] 转换为 int
long2bytes将 long 转换为 byte[]
bytes2long将 byte[] 转换为 long
float2bytes将 float 转换为 byte[]
bytes2float将 byte[] 转换为 float
double2bytes将 double 转换为 byte[]
bytes2double将 byte[] 转换为 double
+

字符串工具

+

StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。

+

截取字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.substr("abcde", 1); // bcde
+String result = StringUtils.substr("abcde", 1, 2); // bc
+
+

生成随机字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.random(length);
+
+

比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equals("abcd", "abce", 3); // true
+boolean result = StringUtils.equals("abcd", "abce", 4); // false
+
+

忽略大小写比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equalsIgnoreCase("abcd", "Abce", 3); // true
+boolean result = StringUtils.equalsIgnoreCase("abcd", "aBce", 4); // false
+
+

拼音工具

+

PinyinUtils 封装了获取中文拼音、拼音首字母的方法。

+
import com.buession.core.utils.PinyinUtils;
+
+String result = PinyinUtils.getPinyin("中国"); // zhongguo
+String result = PinyinUtils.getPinYinFirstChar("中国"); // zg
+
+

随机数工具

+

RandomUtils 封装了随机数的生成。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
nextBoolean随机布尔值
nextBytes随机字节数组
nextInt生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE
nextLong生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE
nextFloat生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE
nextDouble生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE
+

Properties 工具

+

PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。

+
import com.buession.core.utils.SystemPropertyUtils;
+
+Integer result = PropertiesUtils.getInteger(properties, key);
+Boolean result = PropertiesUtils.getBoolean(properties, key);
+
+

System Property 工具

+

SystemPropertyUtils 封装了系统属性或系统环境变量的操作。

+

设置属性方法 setPropertySystem.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty

+
import com.buession.core.utils.SystemPropertyUtils;
+
+SystemPropertyUtils.setProperty("http.port", 8080);
+SystemPropertyUtils.setProperty("http.ssl.enable", false);
+
+

获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。

+
String value = System.getProperty(name);
+
+if(Validate.hasText(value) == false){
+  value = System.getenv(name);
+}
+
+

版本工具

+

VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。

+
import com.buession.core.utils.VersionUtils;
+
+VersionUtils.compare("1.0.0", "1.0.1-beta"); // -1
+VersionUtils.compare("1.0.0", "1.0.0r"); // -1
+
+

规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本

+

获取类的版本值

+
import com.buession.core.utils.VersionUtils;
+
+ByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.1.0
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/core/validator.html b/manual/2.1/core/validator.html new file mode 100644 index 0000000..cbb7fbb --- /dev/null +++ b/manual/2.1/core/validator.html @@ -0,0 +1,239 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

验证器

+

数据验证器及其注解。

+

该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。

+

并提供对应的基于 javax.validation 的校验注解。

+

验证是否为 null

+

判断任意对象是否为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNull(obj);
+
+

验证是否不为 null

+

判断任意对象是否不为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNotNull(obj);
+
+

判断字符串是否为空白字符串

+

判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 false

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isBlank(str); // true
+
+String str = "";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\n";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\na";
+boolean result = Validate.isBlank(str); // false
+
+
    +
  • 注:isNotBlank 与之相反
  • +
+

判断是否为空

+

isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isEmpty(str); // true
+
+String str = " ";
+boolean result = Validate.isEmpty(str); // false
+
+boolean result = Validate.isEmpty(new String[]{}); // true
+
+boolean result = Validate.isEmpty(new Integer[]{1}); // false
+
+
    +
  • 注:isNotEmpty 与之相反
  • +
+

判断是否在两个数之间

+

isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3); // true
+
+boolean result = Validate.isBetween(2, 2, 3); // false
+
+

可通过参数设置是否包含边界值

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3, true); // true
+
+boolean result = Validate.isBetween(2, 2, 3, true); // true
+
+

判断是否为电话号码

+

isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isTel("028-12345678"); // true
+
+boolean result = Validate.isTel("028-02345678"); // false
+
+

判断是否为手机号码

+

isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isMobile("028-12345678"); // false
+
+boolean result = Validate.isMobile("13800138000"); // true
+
+

判断是否为邮政编码

+

isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isPostCode("043104"); // false
+
+boolean result = Validate.isPostCode("643104"); // true
+
+

判断是否为 QQ 号码

+

isQQ 可判断一个字符串是否为 QQ 号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isQQ("043104"); // false
+
+boolean result = Validate.isQQ("25132.141"); // true
+
+

判断是否为身份证号码

+

isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+

可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx", true, "2.10-01-01");
+
+

其它,更多方法可以参考手册

+

注解

+

javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解验证的数据类型说明
@AlnumCharSequence 的子类型,Character验证注解的元素值是否为数字
@AlphaCharSequence 的子类型,Character验证注解的元素值是否为数字
@NumericCharSequence 的子类型,Character验证注解的元素值是否为数字
@Betweenshort、int、double 等任何 Number 的子类型验证注解的元素值是否为在两个数之间
@EmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否为空
@NotEmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否不为空
@HasTextCharSequence 的子类型验证注解的元素值是否有非空字符
@IDCardCharSequence 的子类型验证注解的元素值是否有非空字符
@IpCharSequence 的子类型验证注解的元素值是否有非空字符
@IsbnCharSequence 的子类型验证注解的元素值是否有非空字符
@MimeTypeCharSequence 的子类型验证注解的元素值是否有非空字符
@MobileCharSequence 的子类型验证注解的元素值是否有非空字符
@Null任意类型验证注解的元素值是否为 null
@NotNull任意类型验证注解的元素值是否为 null
@PortInteger验证注解的元素值是否为 null
@PostCodeCharSequence 的子类型验证注解的元素值是否为 null
@QQCharSequence 的子类型验证注解的元素值是否为 null
@TelCharSequence 的子类型验证注解的元素值是否为 null
@XdigitCharSequence 的子类型验证注解的元素值是否为 null
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/cron/index.html b/manual/2.1/cron/index.html new file mode 100644 index 0000000..25f3556 --- /dev/null +++ b/manual/2.1/cron/index.html @@ -0,0 +1,30 @@ +buession-cron 参考手册-参考手册

buession-cron 参考手册

+

对 quartz 的二次封装

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-cron</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。

+

由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。

+

且在今后的版本中,该模块可能会被废弃。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/dao/index.html b/manual/2.1/dao/index.html new file mode 100644 index 0000000..3729cb4 --- /dev/null +++ b/manual/2.1/dao/index.html @@ -0,0 +1,46 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-dao</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。

+

同时,我们在代码层面实现了数据库的读写分离。

+

我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。

+

Dao 接口

+

接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.1.0/com/buession/dao/Dao.html

+
public interface Dao<P, E> {
+}
+
+
    +
  • P:主键类型
  • +
  • E:实体类
  • +
+

分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset

+

条件为 Map<String, Object> 类型,允许为 null。

+

排序为 Map<String, com.buession.lang.Order> 类型,允许为 null。

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/dao/mongodb.html b/manual/2.1/dao/mongodb.html new file mode 100644 index 0000000..2513c03 --- /dev/null +++ b/manual/2.1/dao/mongodb.html @@ -0,0 +1,24 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplateslaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

AbstractMongoDBDaoreplace 执行的也是 insert。 +在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/dao/mybatis.html b/manual/2.1/dao/mybatis.html new file mode 100644 index 0000000..e22aa6f --- /dev/null +++ b/manual/2.1/dao/mybatis.html @@ -0,0 +1,170 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplateslaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

Mybatis 约定

+
    +
  1. 如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace
  2. +
+
namespace com.buession.dao.test.dao;
+
+public class UserDaoImpl extends AbstractMyBatisDao<Integer, User> {
+
+	@Override
+	protected String getStatement(){
+		return "com.buession.dao.test.dao.UserMapper";
+	}
+
+}
+
+
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.buession.dao.test.dao.UserMapper">
+</mapper>
+
+
    +
  1. Mapper 的 SQL ID 和方法名保持一致
  2. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQL ID说明返回值
insert插入数据影响的行数
batchInsert批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入每次插入影响的行数列表
replace替换数据,即:REPLACE 语句影响的行数
batchReplace批量替换数据,即:REPLACE 语句每次替换数据影响的行数列表
update更新数据更新条数
updateByPrimary根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值更新条数
getByPrimary根据主键查询数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
selectOne(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
select查询数据数据结果列表
getAll查询所有数据数据结果列表
count获取记录数记录数
deleteByPrimary根据主键删除数据影响条数
delete删除数据影响条数
clear清除数据影响条数
truncate截断数据影响条数
+
    +
  • 注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。 +以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。
  • +
+

Mybatis 类型处理器

+

MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeHandler说明
DefaultEnumTypeHandler默认 Enum 类型处理器,将值直接转换为枚举字段
IgnoreCaseEnumTypeHandler忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段
DefaultJsonTypeHandlerJSON 处理器,将 JSON 格式的字符串值和类型 <E> 进行转换
DefaultSetEnumTypeHandler默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素
IgnoreCaseSetEnumTypeHandler忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素
DefaultSetTypeHandler默认 Set 类型处理器,将值以 "," 拆分转换为 Set<String>
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/geoip/index.html b/manual/2.1/geoip/index.html new file mode 100644 index 0000000..7f766f3 --- /dev/null +++ b/manual/2.1/geoip/index.html @@ -0,0 +1,79 @@ +buession-geoip 参考手册-参考手册

buession-geoip 参考手册

+

对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-geoip</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。

+

geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。

+

获取国家信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Country country = resolver.country("114.114.114.114");
+// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}
+
+Country country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8
+// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}
+
+

获取城市信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+District district = resolver.district("114.114.114.114");
+// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}
+
+District district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8
+// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}
+
+

获取位置信息

+

位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Location location = resolver.location("114.114.114.114");
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}
+
+Location location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}
+
+

缓存

+

为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。

+

Resolver 的 Spring Factory Bean

+

我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。

+
<bean id="geoIPResolver" class="com.buession.geoip.spring.GeoIPResolverFactoryBean"
+  p:dbPath="/data/maxmind/City.mmdb"
+  p:stream-ref="dbStream"
+  p:enableCache="true/false"
+ />
+
+
    +
  1. dbPathstream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。
  2. +
  3. enableCache 可以控制是否缓存。
  4. +
+

关于 IP 库

+

buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/httpclient/configuration.html b/manual/2.1/httpclient/configuration.html new file mode 100644 index 0000000..233e2e0 --- /dev/null +++ b/manual/2.1/httpclient/configuration.html @@ -0,0 +1,139 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接配置

+

您可以通过连接配置类 Configuration 配置 apache httpcomponentsokhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponentsokhttp3 的配置信息。

+

配置属性说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名称数据类型apache httpcomponents 对应配置okhttp3 对应配置默认值说明
maxConnectionsintmaxTotalmaxIdleConnections5000最大连接数
maxPerRouteintdefaultMaxPerRoute--500每个路由的最大连接数
idleConnectionTimeintcloseIdleConnectionskeepAliveDuration60000空闲连接存活时长(单位:毫秒)
connectTimeoutintconnectTimeoutconnectTimeout3000连接超时时间(单位:毫秒)
connectionRequestTimeoutintconnectionRequestTimeout--5000从连接池获取连接的超时时间(单位:毫秒)
readTimeoutintsocketTimeoutreadTimeout5000读取超时时间(单位:毫秒)
allowRedirectsBooleanredirectsEnabledfollowRedirects--是否允许重定向
relativeRedirectsAllowedBooleanrelativeRedirectsAllowed----是否应拒绝相对重定向
circularRedirectsAllowedBooleancircularRedirectsAllowed----是否允许循环重定向
maxRedirectsIntegermaxRedirects----最大允许重定向次数
authenticationEnabledbooleanauthenticationEnabled----是否开启 Http Basic 认证
contentCompressionEnabledbooleancontentCompressionEnabled----是否启用内容压缩
normalizeUribooleannormalizeUri----是否标准化 URI
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/httpclient/connectionmanager.html b/manual/2.1/httpclient/connectionmanager.html new file mode 100644 index 0000000..c323454 --- /dev/null +++ b/manual/2.1/httpclient/connectionmanager.html @@ -0,0 +1,23 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接管理器

+

连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponentsokhttp3 的文档。

+

您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponentsokhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。

+

关于 okhttp 连接管理器

+

okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/httpclient/index.html b/manual/2.1/httpclient/index.html new file mode 100644 index 0000000..aa724d7 --- /dev/null +++ b/manual/2.1/httpclient/index.html @@ -0,0 +1,115 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

apache httpcomponentsokhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponentsokhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。

+

传统的方式:

+
<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import org.apache.http.HttpResponse;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.client.methods.HttpPost;
+
+HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();
+
+HttpResponse response = httpClient.execute(new HttpPost("https://www.buession.com/"));
+
+

或者

+
<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import okhttp3.HttpClientConnectionManager;
+import okhttp3.OkHttpClient;
+import okhttp3.ConnectionPool;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+
+OkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());
+HttpClient httpClient = builder.build();
+
+Builder requestBuilder = new Builder().post();
+requestBuilder.url("https://www.buession.com/");
+Request okHttpRequest = requestBuilder.build();
+
+Response httpResponse = httpClient.newCall(okHttpRequest).execute();
+
+

现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

或者

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.OkHttpHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.conn.OkHttpClientConnectionManager;
+import com.buession.httpclient.core.Response;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+
+

展望

+

目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.2) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/httpclient/method.html b/manual/2.1/httpclient/method.html new file mode 100644 index 0000000..f7ebb39 --- /dev/null +++ b/manual/2.1/httpclient/method.html @@ -0,0 +1,158 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

方法

+

buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。

+

示例:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/");
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/");
+
+

您可以自定义请求头:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.List;
+import java.util.ArrayList;
+
+List<Header> headers = new ArrayList<>();
+
+headers.add(new Header("X-SDK-NAME", "Buession"));
+headers.add(new Header("X-Timestamp", System.currentTimeMillis()));
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", headers);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", headers);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", headers);
+
+

您可以设置请求参数:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> parameters = new HashMap<>();
+
+parameters.put("action", "edit");
+parameters.put("id", 1);
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", parameters);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", parameters);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", parameters);
+
+

您可以设置请求体:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import jcom.buession.httpclient.core.RequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+
+EncodedFormRequestBody requestBody = new EncodedFormRequestBody();
+
+requestBody.addRequestBodyElement("username", "buession");
+requestBody.addRequestBodyElement("password", "buession");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", requestBody);
+
+JsonRawRequestBody requestBody = new JsonRawRequestBody(new User());
+// PUT 请求
+Response response = httpClient.put("https://www.buession.com/", requestBody);
+
+

不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody

+

RequestBody

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RequestBodyContent-Type说明
InputStreamRequestBodyapplication/octet-stream二进制请求体
ChunkedInputStreamRequestBodyapplication/octet-streamChunked 二进制请求体
RepeatableInputStreamRequestBodyapplication/octet-streamRepeatable 二进制请求体
EncodedFormRequestBodyapplication/x-www-form-urlencoded普通表单请求体
MultipartFormRequestBodymultipart/form-data文件上传表单请求体
HtmlRawRequestBodytext/htmlHTML 请求体
JavaScriptRawRequestBodyapplication/javascriptJavaScript 请求体
JsonRawRequestBodyapplication/jsonJSON 请求体
TextRawRequestBodytext/plainTEXT 请求体
XmlRawRequestBodytext/xmlXML 请求体
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/httpclient/response.html b/manual/2.1/httpclient/response.html new file mode 100644 index 0000000..a97dd3f --- /dev/null +++ b/manual/2.1/httpclient/response.html @@ -0,0 +1,37 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

响应

+

当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。 +buession-httpclient 会将 apache httpcomponentsokhttp3 的响应对象,转换为 Response

+

需要注意的是,原生 apache httpcomponentsokhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.core.Response;
+import java.io.InputStream;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+InputStream stream = response.getInputStream(); // 以流的形式获取响应体
+String body = response.getBody(); // 以字符串的形式获取响应体
+
+stream.close();
+
+

getInputStreamgetBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponentsokhttp3 返回的流。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/index.html b/manual/2.1/index.html new file mode 100644 index 0000000..4656313 --- /dev/null +++ b/manual/2.1/index.html @@ -0,0 +1,119 @@ +API 参考手册-参考手册

API 参考手册

+

Buession Framework API 包含以下目录:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
模块使用帮助手册
buession-aop使用帮助API 手册
buession-beans使用帮助API 手册
buession-core使用帮助API 手册
buession-cron使用帮助API 手册
buession-dao使用帮助API 手册
buession-geoip使用帮助API 手册
buession-git使用帮助API 手册
buession-httpclient使用帮助API 手册
buession-io使用帮助API 手册
buession-jdbc使用帮助API 手册
buession-json使用帮助API 手册
buession-lang使用帮助API 手册
buession-net使用帮助API 手册
buession-redis使用帮助API 手册
buession-session使用帮助API 手册
buession-thesaurus使用帮助API 手册
buession-velocity使用帮助API 手册
buession-web使用帮助API 手册
+
\ No newline at end of file diff --git a/manual/2.1/io/index.html b/manual/2.1/io/index.html new file mode 100644 index 0000000..c3f21ba --- /dev/null +++ b/manual/2.1/io/index.html @@ -0,0 +1,86 @@ +buession-io 参考手册-参考手册

buession-io 参考手册

+

封装了对文件的操作

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-io</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块二次封装了 java java.io.Filejava.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。

+

读取文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+byte[] result = file.read();
+
+

写文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+file.write("Buession");
+file.write("Buession".getBytes());
+file.write("Buession", true); // 追加写
+
+

获取文件 MD5、SHA-1值

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+String md5 = file.getMd5(); // 获取文件 MD5
+String sha1 = file.getSha1(); // 获取文件 SHA-1
+
+

获取文件 MD5、SHA-1 值

+
import com.buession.io.file.File;
+import com.buession.io.MimeType;
+
+File file = new File("/tmp/debug.txt");
+
+MimeType result = file.getMimeType();
+
+

设置文件权限

+
import com.buession.io.file.Files;
+
+Files.chmod("/tmp/debug.txt", 0777);
+
+

设置文件用户组

+
import com.buession.io.file.Files;
+
+Files.chgrp("/tmp/debug.txt", "root");
+
+

设置文件用户

+
import com.buession.io.file.Files;
+
+Files.chown("/tmp/debug.txt", "root");
+
+

注解

+

注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。

+
import com.buession.io.json.annotation.MimeTypeString;
+
+class File {
+
+    @MimeTypeString
+    private MimeType mime;
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/jdbc/index.html b/manual/2.1/jdbc/index.html new file mode 100644 index 0000000..47cf905 --- /dev/null +++ b/manual/2.1/jdbc/index.html @@ -0,0 +1,28 @@ +buession-jdbc 参考手册-参考手册

buession-jdbc 参考手册

+

JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-jdbc</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通过提供的 API,您可以简化对 DBCP2DruidHikariTomcat 数据源的初始化,该类库基本不单独使用。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/json/index.html b/manual/2.1/json/index.html new file mode 100644 index 0000000..92c6037 --- /dev/null +++ b/manual/2.1/json/index.html @@ -0,0 +1,63 @@ +buession-json 参考手册-参考手册

buession-json 参考手册

+

主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-json</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

封装了大量基于 jackson 的注解。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解说明
CalendarUnixTimestampjava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar
DateUnixTimestampjava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date
SqlDateUnixTimestampjava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date
TimestampUnixTimestampjava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp
JsonEnum2Map枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举
Sensitive通过该注解可以实现数据的脱敏
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/lang/index.html b/manual/2.1/lang/index.html new file mode 100644 index 0000000..cc526b9 --- /dev/null +++ b/manual/2.1/lang/index.html @@ -0,0 +1,27 @@ +buession-lang 参考手册-参考手册

buession-lang 参考手册

+

常用 POJO 类和枚举的定义,详细查看 API 参考手册。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-lang</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/net/index.html b/manual/2.1/net/index.html new file mode 100644 index 0000000..e7c119f --- /dev/null +++ b/manual/2.1/net/index.html @@ -0,0 +1,35 @@ +buession-net 参考手册-参考手册

buession-net 参考手册

+

网络相关工具类。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-net</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

IP 地址工具类

+

IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。

+
import com.buession.net.utils.InetAddressUtis;
+
+long result = InetAddressUtis.ip2long("127.0.0.1"); // 2130706433
+String ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1
+
+

URI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/redis/datasource.html b/manual/2.1/redis/datasource.html new file mode 100644 index 0000000..f41a1a3 --- /dev/null +++ b/manual/2.1/redis/datasource.html @@ -0,0 +1,185 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

数据源

+

buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。 +通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。

+

数据源 DataSource 包括三个子接口:

+
    +
  • StandaloneDataSource:单机模式数据源
  • +
  • SentinelDataSource:哨兵模式数据源
  • +
  • ClusterDataSource:集群模式数据源
  • +
+

jedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。

+

在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。

+
<bean id="redisDataSource" class="com.buession.redis.client.connection.datasource.jedis.UserMapper"
+	p:host="${redis.host}"
+	p:port="${redis.port}"
+	p:password="${redis.password}" />
+
+

测试环境 properties:

+
redis.host=127.0.0.1
+redis.port=6379
+redis.password=
+
+

生产环境 properties:

+
redis.host=192.168.100.131
+redis.port=6379
+redis.password=passwd
+
+

连接池

+

通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfigConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。

+

连接池配置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项数据类型-- 默认值说明
lifobooleanGenericObjectPoolConfig.DEFAULT_LIFO池模式,为 true 时,后进先出;为 false 时,先进先出
fairnessbooleanGenericObjectPoolConfig.DEFAULT_FAIRNESS当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制
maxWaitDurationGenericObjectPoolConfig.DEFAULT_MAX_WAIT当连接池资源用尽后,调用者获取连接时的最大等待时间
minEvictableIdleTimeDuration60000连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除
softMinEvictableIdleTimeDurationGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数
evictionPolicyClassNameStringGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME驱逐策略的类名
evictorShutdownTimeoutDurationGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT关闭驱逐线程的超时时间
numTestsPerEvictionRunint-1检测空闲对象线程每次运行时检测的空闲对象的数量
testOnCreatebooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE在创建对象时检测对象是否有效,配置 true 会降低性能
testOnBorrowbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW在从对象池获取对象时是否检测对象有效,配置 true 会降低性能
testOnReturnbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能
testWhileIdlebooleantrue在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
blockWhenExhaustedbooleanGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
jmxEnabledbooleanGenericObjectPoolConfig.DEFAULT_JMX_ENABLE是否注册 JMX
jmxNamePrefixStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIXJMX 前缀
jmxNameBaseStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE使用 base + jmxNamePrefix + i 来生成 ObjectName
maxTotalintGenericObjectPoolConfig.DEFAULT_MAX_TOTAL最大连接数
minIdleintGenericObjectPoolConfig.DEFAULT_MIN_IDLE最小空闲连接数
maxIdleintGenericObjectPoolConfig.DEFAULT_MAX_IDLE最大空闲连接数
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/redis/index.html b/manual/2.1/redis/index.html new file mode 100644 index 0000000..27a184e --- /dev/null +++ b/manual/2.1/redis/index.html @@ -0,0 +1,51 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-redis</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

介绍

+

buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。

+
import com.buession.redis.RedisTemplate;
+import com.buession.redis.core.Options;
+import com.buession.core.serializer.type.TypeReference;
+import java.utils.Map;
+import java.utils.HashMap;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.setOptions(new Options());
+redisTemplate.afterPropertiesSet();
+
+// 将 User 对象写进 key 为 user hash 中
+redisTemplate.hSet("user", "1", new User());
+
+// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User
+User user = redisTemplate.hGetObject("user", "1", User.class);
+
+// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User
+Map<String, User> data = redisTemplate.hGetAllObject("user", "1", new TypeReference<HashMap<String, User>>{});
+
+

展望

+

目前,buession-redis 仅支持 jedis,不支持 lettuce,我们预计会在下个版本或者下下个版本(即:2.1 或者 2.2)中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/redis/method.html b/manual/2.1/redis/method.html new file mode 100644 index 0000000..a988848 --- /dev/null +++ b/manual/2.1/redis/method.html @@ -0,0 +1,49 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

方法

+

buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。

+
import com.buession.redis.BaseRedisTemplate;
+
+BaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 删除哈希表 key 中的一个或多个指定域
+redisTemplate.hDel("user", "1", "2", "3");
+
+// 检查给定 key 是否存在
+redisTemplate.exists("user");
+
+// 获取列表 key 中,下标为 index 的元素
+redisTemplate.lIndex("user", 1);
+
+// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾
+redisTemplate.append("key", "value 1");
+
+

BaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。

+
import com.buession.redis.RedisTemplate;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类
+User user = redisTemplate.lIndexObject("user", 1, User.class);
+
+

序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/session/index.html b/manual/2.1/session/index.html new file mode 100644 index 0000000..a4475da --- /dev/null +++ b/manual/2.1/session/index.html @@ -0,0 +1,28 @@ +buession-session 参考手册-参考手册

buession-session 参考手册

+

无文档

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-session</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块无实际意义,将在今后的版本中会删除掉。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/thesaurus/index.html b/manual/2.1/thesaurus/index.html new file mode 100644 index 0000000..fbb568f --- /dev/null +++ b/manual/2.1/thesaurus/index.html @@ -0,0 +1,37 @@ +buession-thesaurus 参考手册-参考手册

buession-thesaurus 参考手册

+

对词库的解析,目前仅支持搜狗词条。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-thesaurus</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。

+
import com.buession.thesaurus.SogouParser;
+import com.buession.thesaurus.Parser;
+import com.buession.thesaurus.core.Word;
+import java.util.Set;
+
+Parser parser = new SogouParser();
+
+Set<Word> words parser.parse("搜谱拼音词条文件路径");
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/velocity/index.html b/manual/2.1/velocity/index.html new file mode 100644 index 0000000..666c80b --- /dev/null +++ b/manual/2.1/velocity/index.html @@ -0,0 +1,28 @@ +buession-velocity 参考手册-参考手册

buession-velocity 参考手册

+

spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-velocity</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/web/annotation.html b/manual/2.1/web/annotation.html new file mode 100644 index 0000000..e58c325 --- /dev/null +++ b/manual/2.1/web/annotation.html @@ -0,0 +1,152 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

注解

+

我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解Request / Response作用域说明
@RequestClientIprequest方法参数获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress
@ContentTyperesponse类、方法设置响应 Content-Type
@HttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值
@DisableHttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存
@ResponseHeaderresponse类、方法设置响应头
@ResponseHeadersresponse类、方法批量设置响应头
@DocumentMetaDataresponse类、方法设置页面标题、页面编码、关键字、描述、版权等等元信息
+

获取用户端真实 IP

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

设置页面缓存

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

以上,会自动计算 Cache-Controlpragma 的值。当然,您也可以手动指定。

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5", cacheControl="public, max-age=5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.1/web/filter.html b/manual/2.1/web/filter.html new file mode 100644 index 0000000..8d3e0b8 --- /dev/null +++ b/manual/2.1/web/filter.html @@ -0,0 +1,51 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

过滤器

+

我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。

+

servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。

+

过滤器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
过滤器说明
PoweredByFilterPowered By 过滤器
PrintUrlFilter打印当前请求 URL 过滤器
ResponseHeaderFilter响应头过滤器,设置响应头
ResponseHeadersFilter响应头过滤器,批量设置响应头
ServerInfoFilterServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点
+
\ No newline at end of file diff --git a/manual/2.1/web/index.html b/manual/2.1/web/index.html new file mode 100644 index 0000000..381ac08 --- /dev/null +++ b/manual/2.1/web/index.html @@ -0,0 +1,28 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-web</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

buession-web 扩展了 spring-webmvcspring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.1/web/restful.html b/manual/2.1/web/restful.html new file mode 100644 index 0000000..cfe3ce1 --- /dev/null +++ b/manual/2.1/web/restful.html @@ -0,0 +1,47 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

RESTFUL

+

Restful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。

+

我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。

+
@RestController
+@RequestMapping(path = "/example")
+public class ExampleController extends AbstractRestController<Integer, ExampleDto, ExampleVo> {
+
+	@Override
+	public Response<ExampleVo> add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){
+		
+	}
+
+	@Override
+	public Response<ExampleVo> edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id, @RequestBody ExampleDto example){
+
+	}
+
+	@Override
+	public Response<ExampleVo> detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+
+	}
+
+	@Override
+	public Response<ExampleVo> delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.1/web/utils.html b/manual/2.1/web/utils.html new file mode 100644 index 0000000..f05b2f4 --- /dev/null +++ b/manual/2.1/web/utils.html @@ -0,0 +1,35 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

工具

+

我们封装了一些 web 相关的工具类,用于处理 request、response。

+

servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。

+

获取客户端真实 IP 地址:

+
RequestUtils.getClientIp(request);
+
+

我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。

+

优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr

+

是否是 Ajax 请求:

+
RequestUtils.isAjaxRequest(request);
+
+

是否是移动设备请求:

+
RequestUtils.isMobile(request);
+
+

设置缓存:

+
ResponseUtils.httpCache(response, 5); // 缓存 5 秒
+ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点
+
+
\ No newline at end of file diff --git a/manual/2.2/aop/index.html b/manual/2.2/aop/index.html new file mode 100644 index 0000000..0ff89f1 --- /dev/null +++ b/manual/2.2/aop/index.html @@ -0,0 +1,27 @@ +buession-aop 参考手册-参考手册

buession-aop 参考手册

+

AOP 封装,方便实现自定义注解

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-aop</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/beans/index.html b/manual/2.2/beans/index.html new file mode 100644 index 0000000..f357d55 --- /dev/null +++ b/manual/2.2/beans/index.html @@ -0,0 +1,93 @@ +buession-beans 参考手册-参考手册

buession-beans 参考手册

+

该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-beans</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

属性拷贝

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.copyProperties(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.copyProperties(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

属性映射

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.populate(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.populate(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

Bean 转换为 Map

+

使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Key

+
import com.buession.beans.BeanUtils;
+
+Map<String, Object> result = BeanUtils.toMap(bean)
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/builder.html b/manual/2.2/core/builder.html new file mode 100644 index 0000000..1343103 --- /dev/null +++ b/manual/2.2/core/builder.html @@ -0,0 +1,140 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

Map、集合的便捷式构建,减少您的代码行数。

+

您需要往 Map、List 中添加元素的传统写法是:

+
import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+List<String> list = new ArrayList<>();
+list.add("A");
+list.add("B");
+list.add("C");
+
+Map<String, Object> map = new HashMap<>();
+map.put("a", "A");
+map.put("b", "B");
+map.put("c", "C");
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.Map;
+
+List<String> list = ListBuilder.<String>create().add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create().put("a", "A").put("b", "B").put("c", "C");
+
+

此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+List<String> list = ListBuilder.<String>create(LinkedList.class).add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create(LinkedHashMap.class).put("a", "A").put("b", "B").put("c", "C");
+
+
    +
  • 注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数
  • +
+

当您有 value 为 null 时,不添加到 List 时,传统写法:

+
import java.util.ArrayList;
+import java.util.List;
+
+String value = null;
+List<String> list = new ArrayList<>();
+
+if(value != null){
+	list.add(value);
+}
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+String value = null;
+List<String> list = ListBuilder.<String>create().addIfPresent(value).build();
+
+

Map、Set、Queue 同理。

+

便捷方法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
List ListBuilder.epmty()创建空的 V 类型的 List 对象
List ListBuilder.of()创建空的 V 类型的 List 对象
List ListBuilder.of(V value)创建仅有一个元素的 V 类型的 List 对象
Queue QueueBuilder.epmty()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of(V value)创建仅有一个元素的 V 类型的 Queue 对象
Set SetBuilder.epmty()创建空的 V 类型的 Set 对象
Set SetBuilder.of()创建空的 V 类型的 Set 对象
Set SetBuilder.of(V value)创建仅有一个元素的 V 类型的 Set 对象
<K, V> Map<K, V> MapBuilder.epmty()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of(V value)创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象
+

empty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/codec.html b/manual/2.2/core/codec.html new file mode 100644 index 0000000..97a29ef --- /dev/null +++ b/manual/2.2/core/codec.html @@ -0,0 +1,82 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。

+

我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。

+

此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。

+

此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。

+
USER_NOT_FOUND.code = 10404
+USER_NOT_FOUND.message = 用户不存在
+
+USER_LOGIN_FAILURE.code = 10405
+USER_LOGIN_FAILURE.message = 登录失败
+
+
<context:property-placeholder location="classpath:error_message.properties"/>
+
+<util:properties location="classpath:error_message.properties" local-override="true"/>
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message("USER_NOT_FOUND")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。

+
USER_NOT_FOUND.errorCode = 10404
+USER_NOT_FOUND.errorMessage = 用户不存在
+
+USER_LOGIN_FAILURE.errorCode = 10405
+USER_LOGIN_FAILURE.errorMessage = 登录失败
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message(value = "USER_NOT_FOUND", code = "errorCode", message = "errorMessage")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/collect.html b/manual/2.2/core/collect.html new file mode 100644 index 0000000..7955ab6 --- /dev/null +++ b/manual/2.2/core/collect.html @@ -0,0 +1,160 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

收集器

+

数组、Map、集合的工具类

+

数组

+

数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 ListSet 以及字符串类型的数组、数组合并、数组元素操作等方法。

+

检测数组 array 中是否存在元素 element:

+
import com.buession.core.collect.Arrays;
+
+boolean result = Arrays.contains(array, element);
+
+

返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.indexOf(array, element);
+
+

返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.lastIndexOf(array, element);
+
+

将字符串拼接会字符串:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String result = Arrays.toString(array);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String glue = "-";
+String result = Arrays.toString(array, glue);
+// 1-2-3
+
+

可以通过方法 toList、toSet 转换为 List 和 Set:

+
import com.buession.core.collect.Arrays;
+import java.util.List;
+import java.util.Set;
+
+int[] array = {1, 2, 3};
+List<Integer> list = Arrays.toList(array);
+Set<Integer> set = Arrays.toSet(array);
+
+

将数组转换为字符串类型的数组:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String[] result = Arrays.toStringArray(array);
+
+

将数组进行合并:

+
import com.buession.core.collect.Arrays;
+
+String[] result = Arrays.toStringArray(array1, array2, array3);
+
+

对数组元素进行操作:

+
import com.buession.core.collect.Arrays;
+
+String[] array = {"A", "B", "C"};
+String[] result = Arrays.map(array, String.class, fn);
+
+

第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现

+

Lists

+

List 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Lists.toString(list);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Lists.toString(list);
+// 1-2-3
+
+

可以通过方法 toSet 将 List 转换为 Set:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+Set<Integer> set = Lists.toSet(list);
+
+

Sets

+

Sett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Sets.toString(set);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Sets.toString(list);
+// 1-2-3
+
+

可以通过方法 toList 将 Set 转换为 List:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+List<Integer> list = Sets.toList(set);
+
+

Maps

+

Map 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。

+

对 Map 进行操作:

+
import com.buession.core.collect.Maps;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> maps = new HashMap<>();
+Map<String, String> result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());
+
+

第二个、第三参数为 java.util.function.Function 的实现

+

可以通过方法 toList 将 Map 的 value 转换为 List:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+List<T> list = Maps.toList(maps);
+
+

可以通过方法 toSet 将 Map 的 value 转换为 Set:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.Set;
+
+Set<T> set = Maps.toSet(maps);
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/context.html b/manual/2.2/core/context.html new file mode 100644 index 0000000..06edca7 --- /dev/null +++ b/manual/2.2/core/context.html @@ -0,0 +1,91 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

上下文

+

注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。

+

在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:

+
    +
  • 逻辑少
  • +
  • 与 Dao 层进行交互,多个 Dao 层的复用
  • +
  • Service 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层
  • +
+
import com.buession.core.context.stereotype.Manager;
+import org.springframework.stereotype.Service;
+
+public interface UserManager {
+
+	User getByPrimary(int id);
+
+}
+
+@Manager
+public class UserManagerImpl implements UserManager {
+
+	@Autowired
+	private UserDao userDao;
+
+	@Autowired
+	private UserProfileDao userProfileDao;
+
+	@Autowired
+	private RedisTemplate redisTemplate;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = redisTemplate.hGetObject("user", Integer.toString(id), User.class);
+
+		if(user == null){
+			user = userDao.getByPrimary(id);
+			if(user != null){
+				user.setProfile(userProfileDao.getByUserId(id));
+				redisTemplate.hSet("user", Integer.toString(id), user);
+			}else{
+				throw new RuntimeException("用户不存在");
+			}
+		}
+
+		return user;
+	}
+
+}
+
+public interface UserService {
+
+	User getByPrimary(int id);
+
+}
+
+@Service
+public class UserServiceImpl implements UserService {
+
+	@Autowired
+	private UserManager userManager;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = userManager.getByPrimary(id);
+
+		...
+
+		return user;
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/converter.html b/manual/2.2/core/converter.html new file mode 100644 index 0000000..6ec6ee8 --- /dev/null +++ b/manual/2.2/core/converter.html @@ -0,0 +1,159 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

接口定义:

+
@FunctionalInterface
+public interface Converter<S, T> {
+
+	T convert(final S source);
+
+}
+
+

将类似为 S 的对象转换为类型为 T 的对象。

+

内置转换器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
转换器说明
ArrayConverter<S, T>将 S 类型的数组转换为 T 类型的数组
EnumConverter<E extends Enum>枚举转换器,将字符串转换为枚举 E
BinaryEnumConverter<E extends Enum>枚举转换器,将 byte 数组转换为枚举 E
BooleanStatusConverter将布尔值转换为 com.buession.lang.Status
StatusBooleanConvertercom.buession.lang.Status 转换为布尔值
PredicateStatusConverter通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status
ListConverter<S, T>将 S 类型的 List 对象转换为 T 类型的 List 对象
SetConverter<S, T>将 S 类型的 Set 对象转换为 T 类型的 Set 对象
MapConverter<SK, SV, TK, TV>将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map
+

将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象

+
import com.buession.core.converter.MapConverter;
+import java.util.Map;
+
+Map<Integer, Object> source;
+Map<String, String> target;
+MapConverter<Integer, Object, String, String> converter = new MapConverter<>();
+
+target = converter.convert(source);
+
+

将字符串转换为枚举

+
import com.buession.core.converter.EnumConverter;
+import com.buession.lang.Gender;
+
+Gender target;
+EnumConverter<Gender> converter = new EnumConverter<>(Gender.class);
+
+target = converter.convert("FEMALE");
+// Gender.FEMALE
+
+target = converter.convert("F");
+// null
+
+

POJO 类映射

+

我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。

+
public interface Mapper<S, T> {
+
+	/**
+	 * 将源对象映射到目标对象
+	 *
+	 * @param object
+	 * 		源对象
+	 *
+	 * @return 目标对象实例
+	 */
+	T mapping(S object);
+
+	/**
+	 * 将源对象数组映射到目标对象数组
+	 *
+	 * @param object
+	 * 		源对象数组
+	 *
+	 * @return 目标对象实例数组
+	 */
+	T[] mapping(S[] object);
+
+	/**
+	 * 将源 list 对象映射到目标 list 对象
+	 *
+	 * @param object
+	 * 		源 list 对象
+	 *
+	 * @return 目标对象 list 实例
+	 */
+	List<T> mapping(List<S> object);
+
+	/**
+	 * 将源 set 对象映射到目标 set 对象
+	 *
+	 * @param object
+	 * 		源 set 对象
+	 *
+	 * @return 目标对象 set 实例
+	 */
+	Set<T> mapping(Set<S> object);
+
+}
+
+

我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。

+
import com.buession.core.converter.mapper.PropertyMapper;
+
+User source = new User();
+User target = new User();
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
+propertyMapper.form(source::getId).to(target:setId)
+// null
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();
+propertyMapper.form(source::getUsername).to(target:setUsername)
+// null
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/datetime.html b/manual/2.2/core/datetime.html new file mode 100644 index 0000000..43e981c --- /dev/null +++ b/manual/2.2/core/datetime.html @@ -0,0 +1,33 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

日期时间

+

日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。

+

获取当前 Unix 时间戳(秒):

+
import com.buession.core.datetime.DateTime;
+
+DateTime.unixtime();
+
+

该方法我们在实际业务中经常用到。

+

以 "msec sec" 的格式返回当前 Unix 时间戳和微秒数:

+
import com.buession.core.datetime.DateTime;
+
+DateTime.microtime();
+// 1657171717 948000
+
+

该方法参考 PHP 的 microtime 函数而来。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/exception.html b/manual/2.2/core/exception.html new file mode 100644 index 0000000..bb5bd77 --- /dev/null +++ b/manual/2.2/core/exception.html @@ -0,0 +1,70 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

异常

+

通用异常的定义。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
异常说明
AccessException拒绝访问异常
ClassInstantiationException类实例化异常
ConversionException数据类型转换异常
DataAlreadyExistException数据已存在异常
DataNotFoundException数据不存在或未找到异常
InsteadException类方法废弃后,需要使用其它类库方法来替代
NestedRuntimeException嵌套运行时异常
OperationException运算异常
PresentException--
SerializationException序列化异常
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/id.html b/manual/2.2/core/id.html new file mode 100644 index 0000000..6e4920e --- /dev/null +++ b/manual/2.2/core/id.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。

+

接口规范。

+
public interface IdGenerator<T> {
+
+	/**
+	 * 获取下一个 ID
+	 *
+	 * @return ID
+	 */
+	T nextId();
+
+}
+
+

ID 生成器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
生成器说明
AtomicSimpleIdGenerator基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
AtomicUUIDIdGenerator基于 AtomicLong 递增的,UUID ID 生成器
NanoIDIdGeneratorjnanoid ID 生成器,可通过构造函数指定字符范围、长度
RandomDigitIdGenerator随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定
RandomIdGenerator随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位
SimpleIdGenerator简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
SnowflakeIdGenerator雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值
UUIDIdGeneratorUUID ID 生成器
+
import com.buession.core.id.AtomicUUIDIdGenerator;
+import com.buession.core.id.NanoIDIdGenerator;
+import com.buession.core.id.SnowflakeIdGenerator;
+import com.buession.core.id.UUIDIdGenerator;
+import com.buession.core.id.SimpleIdGenerator;
+
+AtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000001
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000002
+
+NanoIDIdGenerator idGenerator = new NanoIDIdGenerator();
+idGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK
+
+SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();
+idGenerator.nextId(); // 170602258864545792
+
+UUIDIdGenerator idGenerator = new UUIDIdGenerator();
+idGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4
+
+SimpleIdGenerator idGenerator = new SimpleIdGenerator();
+idGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05
+
+
    +
  • 注:关于雪花算法、jnanoid 算法的可自行搜索。
  • +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/index.html b/manual/2.2/core/index.html new file mode 100644 index 0000000..0d1d51a --- /dev/null +++ b/manual/2.2/core/index.html @@ -0,0 +1,53 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-core</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

构建器

+

Map、集合的便捷式构建,减少您的代码行数

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中

+

收集器

+

数组、Map、集合的工具类

+

上下文

+

定义应用上下文的类库、注解

+

转换器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

日期时间

+

日期、时间工具

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

数学函数

+

定义了实用的数学函数

+

序列化和反序列化

+

对象的序列化和反序列化,包括二进制和 JSON。

+

验证器

+

数据验证器及其注解

+

工具类

+

常用通用性工具类

+

其它

+

通用的接口定义,框架自身类

+

异常

+

通用异常的定义

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/math.html b/manual/2.2/core/math.html new file mode 100644 index 0000000..ad081b9 --- /dev/null +++ b/manual/2.2/core/math.html @@ -0,0 +1,67 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

数学函数

+

定义了实用的数学函数。

+ + + + + + + + + + + + + + + + + +
方法说明
continuousSum计算两个数之间连续相加之和
rangeValue获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身
+
import com.buession.core.math.Math;
+
+long result = Math.continuousSum(1, 100);
+// 5050
+
+
import com.buession.core.math.Math;
+
+long value = 3;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 4
+
+
import com.buession.core.math.Math;
+
+long value = 5;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 5
+
+
import com.buession.core.math.Math;
+
+long value = 11;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 10
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/other.html b/manual/2.2/core/other.html new file mode 100644 index 0000000..860f0bf --- /dev/null +++ b/manual/2.2/core/other.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

其它

+

通用的接口定义,框架自身类,以及其它杂项。

+

框架自身工具

+

获取 Buession Framework 版本:

+
import com.buession.core.Framework;
+import com.buession.core.BuesssionFrameworkVersion;
+
+BuesssionFrameworkVersion.getVersion(); // 2.2.0
+Framework.VERSION; // 2.2.0
+
+

获取 Buession Framework 框架名称:

+
import com.buession.core.Framework;
+
+Framework.NAME; // "Buession"
+
+

命令执行器

+

命令执行器接口:

+
/**
+ * 命令执行器
+ *
+ * @param <C>
+ * 		命令上下文
+ * @param <R>
+ * 		命令执行返回值
+ */
+@FunctionalInterface
+public interface Executor<C, R> {
+
+	/**
+	 * 命令执行
+	 *
+	 * @param context
+	 * 		命令执行器上下文
+	 *
+	 * @return 命令执行返回值,R 类型的实例
+	 */
+	R execute(C context);
+
+}
+
+

您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。

+

销毁接口

+

功能类似 java.io.Closeable

+
public interface Destroyable {
+
+	/**
+	 * 销毁相关资源
+	 *
+	 * @throws IOException
+	 * 		IO 错误时抛出
+	 */
+	void destroy() throws IOException;
+
+}
+
+

Rawable

+

原始的,约定实现该接口的类,必须返回原始字节数组。

+
public interface Rawable {
+
+	/**
+	 * 返回原始的字节数组
+	 *
+	 * @return 原始的字节数组
+	 */
+	byte[] getRaw();
+
+}
+
+

名称节点

+

名称节点,约定实现该接口的类应该返回一个名称

+
public interface NamedNode {
+
+	/**
+	 * 返回节点名称
+	 *
+	 * @return 节点名称
+	 */
+	@Nullable
+	String getName();
+
+}
+
+

分页

+

com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。

+
\ No newline at end of file diff --git a/manual/2.2/core/serializer.html b/manual/2.2/core/serializer.html new file mode 100644 index 0000000..e232bf1 --- /dev/null +++ b/manual/2.2/core/serializer.html @@ -0,0 +1,56 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

对象的序列化和反序列化,包括二进制和 JSON。

+

您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。

+

序列化、反序列化类

+ + + + + + + + + + + + + + + + + + + + + + + + + +
说明
DefaultByteArraySerializer将对象序列化为二进制,或将二进制反序列化为对象
FastJsonJsonSerializer基于 FastJSON 的对象与 JSON 之间的序列化和反序列化
GsonJsonSerializer基于 Gson 的对象与 JSON 之间的序列化和反序列化
JacksonJsonSerializer基于 Jackson2 的对象与 JSON 之间的序列化和反序列化
+
    +
  1. 通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象
  2. +
  3. DefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象
  4. +
  5. DefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化
  6. +
  7. 在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑
  8. +
  9. FastJsonJsonSerializerGsonJsonSerializerJacksonJsonSerializer 可以通过参数 Class<T>TypeReference<V> 指定返回的对象类型
  10. +
  11. com.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回
  12. +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/utils.html b/manual/2.2/core/utils.html new file mode 100644 index 0000000..501ab95 --- /dev/null +++ b/manual/2.2/core/utils.html @@ -0,0 +1,199 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

工具类

+

常用通用性工具类。

+

Byte 数组比较

+

ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。

+

注解工具

+

AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class<?> clazz, final Class<? extends Annotation>[] annotations)hasMethodAnnotationPresent(Method method, final Class<? extends Annotation>[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。

+

断言

+

Assert 和 springframework 中的注解类似,不过逻辑有些相反。

+

Byte 工具

+

ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] bytes;
+char c = ByteUtils.toChar(bytes);
+
+char[] chars = ByteUtils.toChar(bytes);
+
+

byte 数组连接。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] dest;
+byte[] source
+byte[] result = ByteUtils.concat(dest, source);
+
+

Character 工具

+

CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。

+
import com.buession.core.utils.CharacterUtils;
+
+char c;
+byte[] bytes = ByteUtils.toBytes(c);
+
+char[] chars;
+byte[] bytes = ByteUtils.toBytes(chars);
+
+

数字工具

+

NumberUtils 提供了对数字相关的操作。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
int2bytes将 int 转换为 byte[]
bytes2int将 byte[] 转换为 int
long2bytes将 long 转换为 byte[]
bytes2long将 byte[] 转换为 long
float2bytes将 float 转换为 byte[]
bytes2float将 byte[] 转换为 float
double2bytes将 double 转换为 byte[]
bytes2double将 byte[] 转换为 double
+

字符串工具

+

StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。

+

截取字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.substr("abcde", 1); // bcde
+String result = StringUtils.substr("abcde", 1, 2); // bc
+
+

生成随机字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.random(length);
+
+

比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equals("abcd", "abce", 3); // true
+boolean result = StringUtils.equals("abcd", "abce", 4); // false
+
+

忽略大小写比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equalsIgnoreCase("abcd", "Abce", 3); // true
+boolean result = StringUtils.equalsIgnoreCase("abcd", "aBce", 4); // false
+
+

拼音工具

+

PinyinUtils 封装了获取中文拼音、拼音首字母的方法。

+
import com.buession.core.utils.PinyinUtils;
+
+String result = PinyinUtils.getPinyin("中国"); // zhongguo
+String result = PinyinUtils.getPinYinFirstChar("中国"); // zg
+
+

随机数工具

+

RandomUtils 封装了随机数的生成。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
nextBoolean随机布尔值
nextBytes随机字节数组
nextInt生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE
nextLong生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE
nextFloat生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE
nextDouble生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE
+

Properties 工具

+

PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。

+
import com.buession.core.utils.SystemPropertyUtils;
+
+Integer result = PropertiesUtils.getInteger(properties, key);
+Boolean result = PropertiesUtils.getBoolean(properties, key);
+
+

System Property 工具

+

SystemPropertyUtils 封装了系统属性或系统环境变量的操作。

+

设置属性方法 setPropertySystem.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty

+
import com.buession.core.utils.SystemPropertyUtils;
+
+SystemPropertyUtils.setProperty("http.port", 8080);
+SystemPropertyUtils.setProperty("http.ssl.enable", false);
+
+

获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。

+
String value = System.getProperty(name);
+
+if(Validate.hasText(value) == false){
+  value = System.getenv(name);
+}
+
+

版本工具

+

VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。

+
import com.buession.core.utils.VersionUtils;
+
+VersionUtils.compare("1.0.0", "1.0.1-beta"); // -1
+VersionUtils.compare("1.0.0", "1.0.0r"); // -1
+
+

规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本

+

获取类的版本值

+
import com.buession.core.utils.VersionUtils;
+
+ByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.2.0
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/core/validator.html b/manual/2.2/core/validator.html new file mode 100644 index 0000000..d990b10 --- /dev/null +++ b/manual/2.2/core/validator.html @@ -0,0 +1,239 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

验证器

+

数据验证器及其注解。

+

该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。

+

并提供对应的基于 javax.validation 的校验注解。

+

验证是否为 null

+

判断任意对象是否为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNull(obj);
+
+

验证是否不为 null

+

判断任意对象是否不为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNotNull(obj);
+
+

判断字符串是否为空白字符串

+

判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 false

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isBlank(str); // true
+
+String str = "";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\n";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\na";
+boolean result = Validate.isBlank(str); // false
+
+
    +
  • 注:isNotBlank 与之相反
  • +
+

判断是否为空

+

isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isEmpty(str); // true
+
+String str = " ";
+boolean result = Validate.isEmpty(str); // false
+
+boolean result = Validate.isEmpty(new String[]{}); // true
+
+boolean result = Validate.isEmpty(new Integer[]{1}); // false
+
+
    +
  • 注:isNotEmpty 与之相反
  • +
+

判断是否在两个数之间

+

isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3); // true
+
+boolean result = Validate.isBetween(2, 2, 3); // false
+
+

可通过参数设置是否包含边界值

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3, true); // true
+
+boolean result = Validate.isBetween(2, 2, 3, true); // true
+
+

判断是否为电话号码

+

isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isTel("028-12345678"); // true
+
+boolean result = Validate.isTel("028-02345678"); // false
+
+

判断是否为手机号码

+

isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isMobile("028-12345678"); // false
+
+boolean result = Validate.isMobile("13800138000"); // true
+
+

判断是否为邮政编码

+

isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isPostCode("043104"); // false
+
+boolean result = Validate.isPostCode("643104"); // true
+
+

判断是否为 QQ 号码

+

isQQ 可判断一个字符串是否为 QQ 号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isQQ("043104"); // false
+
+boolean result = Validate.isQQ("25132.141"); // true
+
+

判断是否为身份证号码

+

isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+

可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx", true, "2.10-01-01");
+
+

其它,更多方法可以参考手册

+

注解

+

javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解验证的数据类型说明
@AlnumCharSequence 的子类型,Character验证注解的元素值是否为数字
@AlphaCharSequence 的子类型,Character验证注解的元素值是否为数字
@NumericCharSequence 的子类型,Character验证注解的元素值是否为数字
@Betweenshort、int、double 等任何 Number 的子类型验证注解的元素值是否为在两个数之间
@EmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否为空
@NotEmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否不为空
@HasTextCharSequence 的子类型验证注解的元素值是否有非空字符
@IDCardCharSequence 的子类型验证注解的元素值是否有非空字符
@IpCharSequence 的子类型验证注解的元素值是否有非空字符
@IsbnCharSequence 的子类型验证注解的元素值是否有非空字符
@MimeTypeCharSequence 的子类型验证注解的元素值是否有非空字符
@MobileCharSequence 的子类型验证注解的元素值是否有非空字符
@Null任意类型验证注解的元素值是否为 null
@NotNull任意类型验证注解的元素值是否为 null
@PortInteger验证注解的元素值是否为 null
@PostCodeCharSequence 的子类型验证注解的元素值是否为 null
@QQCharSequence 的子类型验证注解的元素值是否为 null
@TelCharSequence 的子类型验证注解的元素值是否为 null
@XdigitCharSequence 的子类型验证注解的元素值是否为 null
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/cron/index.html b/manual/2.2/cron/index.html new file mode 100644 index 0000000..e0e1e7f --- /dev/null +++ b/manual/2.2/cron/index.html @@ -0,0 +1,30 @@ +buession-cron 参考手册-参考手册

buession-cron 参考手册

+

对 quartz 的二次封装

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-cron</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。

+

由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。

+

且在今后的版本中,该模块可能会被废弃。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/dao/index.html b/manual/2.2/dao/index.html new file mode 100644 index 0000000..ec8969f --- /dev/null +++ b/manual/2.2/dao/index.html @@ -0,0 +1,46 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-dao</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。

+

同时,我们在代码层面实现了数据库的读写分离。

+

我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。

+

Dao 接口

+

接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.2.0/com/buession/dao/Dao.html

+
public interface Dao<P, E> {
+}
+
+
    +
  • P:主键类型
  • +
  • E:实体类
  • +
+

分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset

+

条件为 Map<String, Object> 类型,允许为 null。

+

排序为 Map<String, com.buession.lang.Order> 类型,允许为 null。

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/dao/mongodb.html b/manual/2.2/dao/mongodb.html new file mode 100644 index 0000000..afbf130 --- /dev/null +++ b/manual/2.2/dao/mongodb.html @@ -0,0 +1,24 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplateslaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

AbstractMongoDBDaoreplace 执行的也是 insert。 +在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/dao/mybatis.html b/manual/2.2/dao/mybatis.html new file mode 100644 index 0000000..db948e0 --- /dev/null +++ b/manual/2.2/dao/mybatis.html @@ -0,0 +1,170 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplateslaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

Mybatis 约定

+
    +
  1. 如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace
  2. +
+
namespace com.buession.dao.test.dao;
+
+public class UserDaoImpl extends AbstractMyBatisDao<Integer, User> {
+
+	@Override
+	protected String getStatement(){
+		return "com.buession.dao.test.dao.UserMapper";
+	}
+
+}
+
+
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.buession.dao.test.dao.UserMapper">
+</mapper>
+
+
    +
  1. Mapper 的 SQL ID 和方法名保持一致
  2. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQL ID说明返回值
insert插入数据影响的行数
batchInsert批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入每次插入影响的行数列表
replace替换数据,即:REPLACE 语句影响的行数
batchReplace批量替换数据,即:REPLACE 语句每次替换数据影响的行数列表
update更新数据更新条数
updateByPrimary根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值更新条数
getByPrimary根据主键查询数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
selectOne(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
select查询数据数据结果列表
getAll查询所有数据数据结果列表
count获取记录数记录数
deleteByPrimary根据主键删除数据影响条数
delete删除数据影响条数
clear清除数据影响条数
truncate截断数据影响条数
+
    +
  • 注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。 +以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。
  • +
+

Mybatis 类型处理器

+

MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeHandler说明
DefaultEnumTypeHandler默认 Enum 类型处理器,将值直接转换为枚举字段
IgnoreCaseEnumTypeHandler忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段
DefaultJsonTypeHandlerJSON 处理器,将 JSON 格式的字符串值和类型 <E> 进行转换
DefaultSetEnumTypeHandler默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素
IgnoreCaseSetEnumTypeHandler忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素
DefaultSetTypeHandler默认 Set 类型处理器,将值以 "," 拆分转换为 Set<String>
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/geoip/index.html b/manual/2.2/geoip/index.html new file mode 100644 index 0000000..92431bd --- /dev/null +++ b/manual/2.2/geoip/index.html @@ -0,0 +1,79 @@ +buession-geoip 参考手册-参考手册

buession-geoip 参考手册

+

对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-geoip</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。

+

geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。

+

获取国家信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Country country = resolver.country("114.114.114.114");
+// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}
+
+Country country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8
+// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}
+
+

获取城市信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+District district = resolver.district("114.114.114.114");
+// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}
+
+District district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8
+// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}
+
+

获取位置信息

+

位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Location location = resolver.location("114.114.114.114");
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}
+
+Location location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}
+
+

缓存

+

为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。

+

Resolver 的 Spring Factory Bean

+

我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。

+
<bean id="geoIPResolver" class="com.buession.geoip.spring.GeoIPResolverFactoryBean"
+  p:dbPath="/data/maxmind/City.mmdb"
+  p:stream-ref="dbStream"
+  p:enableCache="true/false"
+ />
+
+
    +
  1. dbPathstream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。
  2. +
  3. enableCache 可以控制是否缓存。
  4. +
+

关于 IP 库

+

buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/git/index.html b/manual/2.2/git/index.html new file mode 100644 index 0000000..dbde1ac --- /dev/null +++ b/manual/2.2/git/index.html @@ -0,0 +1,27 @@ +buession-git 参考手册-参考手册

buession-git 参考手册

+

获取项目 GIT 信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-git</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/httpclient/configuration.html b/manual/2.2/httpclient/configuration.html new file mode 100644 index 0000000..2f8e11c --- /dev/null +++ b/manual/2.2/httpclient/configuration.html @@ -0,0 +1,139 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接配置

+

您可以通过连接配置类 Configuration 配置 apache httpcomponentsokhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponentsokhttp3 的配置信息。

+

配置属性说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名称数据类型apache httpcomponents 对应配置okhttp3 对应配置默认值说明
maxConnectionsintmaxTotalmaxIdleConnections5000最大连接数
maxPerRouteintdefaultMaxPerRoute--500每个路由的最大连接数
idleConnectionTimeintcloseIdleConnectionskeepAliveDuration60000空闲连接存活时长(单位:毫秒)
connectTimeoutintconnectTimeoutconnectTimeout3000连接超时时间(单位:毫秒)
connectionRequestTimeoutintconnectionRequestTimeout--5000从连接池获取连接的超时时间(单位:毫秒)
readTimeoutintsocketTimeoutreadTimeout5000读取超时时间(单位:毫秒)
allowRedirectsBooleanredirectsEnabledfollowRedirects--是否允许重定向
relativeRedirectsAllowedBooleanrelativeRedirectsAllowed----是否应拒绝相对重定向
circularRedirectsAllowedBooleancircularRedirectsAllowed----是否允许循环重定向
maxRedirectsIntegermaxRedirects----最大允许重定向次数
authenticationEnabledbooleanauthenticationEnabled----是否开启 Http Basic 认证
contentCompressionEnabledbooleancontentCompressionEnabled----是否启用内容压缩
normalizeUribooleannormalizeUri----是否标准化 URI
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/httpclient/connectionmanager.html b/manual/2.2/httpclient/connectionmanager.html new file mode 100644 index 0000000..c7b58bf --- /dev/null +++ b/manual/2.2/httpclient/connectionmanager.html @@ -0,0 +1,23 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接管理器

+

连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponentsokhttp3 的文档。

+

您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponentsokhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。

+

关于 okhttp 连接管理器

+

okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/httpclient/index.html b/manual/2.2/httpclient/index.html new file mode 100644 index 0000000..2a2c798 --- /dev/null +++ b/manual/2.2/httpclient/index.html @@ -0,0 +1,115 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

apache httpcomponentsokhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponentsokhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。

+

传统的方式:

+
<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import org.apache.http.HttpResponse;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.client.methods.HttpPost;
+
+HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();
+
+HttpResponse response = httpClient.execute(new HttpPost("https://www.buession.com/"));
+
+

或者

+
<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import okhttp3.HttpClientConnectionManager;
+import okhttp3.OkHttpClient;
+import okhttp3.ConnectionPool;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+
+OkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());
+HttpClient httpClient = builder.build();
+
+Builder requestBuilder = new Builder().post();
+requestBuilder.url("https://www.buession.com/");
+Request okHttpRequest = requestBuilder.build();
+
+Response httpResponse = httpClient.newCall(okHttpRequest).execute();
+
+

现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

或者

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.OkHttpHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.conn.OkHttpClientConnectionManager;
+import com.buession.httpclient.core.Response;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+
+

展望

+

目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.2) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/httpclient/method.html b/manual/2.2/httpclient/method.html new file mode 100644 index 0000000..2639092 --- /dev/null +++ b/manual/2.2/httpclient/method.html @@ -0,0 +1,158 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

方法

+

buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。

+

示例:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/");
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/");
+
+

您可以自定义请求头:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.List;
+import java.util.ArrayList;
+
+List<Header> headers = new ArrayList<>();
+
+headers.add(new Header("X-SDK-NAME", "Buession"));
+headers.add(new Header("X-Timestamp", System.currentTimeMillis()));
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", headers);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", headers);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", headers);
+
+

您可以设置请求参数:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> parameters = new HashMap<>();
+
+parameters.put("action", "edit");
+parameters.put("id", 1);
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", parameters);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", parameters);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", parameters);
+
+

您可以设置请求体:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import jcom.buession.httpclient.core.RequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+
+EncodedFormRequestBody requestBody = new EncodedFormRequestBody();
+
+requestBody.addRequestBodyElement("username", "buession");
+requestBody.addRequestBodyElement("password", "buession");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", requestBody);
+
+JsonRawRequestBody requestBody = new JsonRawRequestBody(new User());
+// PUT 请求
+Response response = httpClient.put("https://www.buession.com/", requestBody);
+
+

不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody

+

RequestBody

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RequestBodyContent-Type说明
InputStreamRequestBodyapplication/octet-stream二进制请求体
ChunkedInputStreamRequestBodyapplication/octet-streamChunked 二进制请求体
RepeatableInputStreamRequestBodyapplication/octet-streamRepeatable 二进制请求体
EncodedFormRequestBodyapplication/x-www-form-urlencoded普通表单请求体
MultipartFormRequestBodymultipart/form-data文件上传表单请求体
HtmlRawRequestBodytext/htmlHTML 请求体
JavaScriptRawRequestBodyapplication/javascriptJavaScript 请求体
JsonRawRequestBodyapplication/jsonJSON 请求体
TextRawRequestBodytext/plainTEXT 请求体
XmlRawRequestBodytext/xmlXML 请求体
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/httpclient/response.html b/manual/2.2/httpclient/response.html new file mode 100644 index 0000000..a889475 --- /dev/null +++ b/manual/2.2/httpclient/response.html @@ -0,0 +1,37 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

响应

+

当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。 +buession-httpclient 会将 apache httpcomponentsokhttp3 的响应对象,转换为 Response

+

需要注意的是,原生 apache httpcomponentsokhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.core.Response;
+import java.io.InputStream;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+InputStream stream = response.getInputStream(); // 以流的形式获取响应体
+String body = response.getBody(); // 以字符串的形式获取响应体
+
+stream.close();
+
+

getInputStreamgetBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponentsokhttp3 返回的流。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/index.html b/manual/2.2/index.html new file mode 100644 index 0000000..1bc7b83 --- /dev/null +++ b/manual/2.2/index.html @@ -0,0 +1,114 @@ +API 参考手册-参考手册

API 参考手册

+

Buession Framework API 包含以下目录:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
模块使用帮助手册
buession-aop使用帮助API 手册
buession-beans使用帮助API 手册
buession-core使用帮助API 手册
buession-cron使用帮助API 手册
buession-dao使用帮助API 手册
buession-geoip使用帮助API 手册
buession-httpclient使用帮助API 手册
buession-io使用帮助API 手册
buession-jdbc使用帮助API 手册
buession-json使用帮助API 手册
buession-lang使用帮助API 手册
buession-net使用帮助API 手册
buession-redis使用帮助API 手册
buession-session使用帮助API 手册
buession-thesaurus使用帮助API 手册
buession-velocity使用帮助API 手册
buession-web使用帮助API 手册
+
\ No newline at end of file diff --git a/manual/2.2/io/index.html b/manual/2.2/io/index.html new file mode 100644 index 0000000..f4ab560 --- /dev/null +++ b/manual/2.2/io/index.html @@ -0,0 +1,86 @@ +buession-io 参考手册-参考手册

buession-io 参考手册

+

封装了对文件的操作

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-io</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块二次封装了 java java.io.Filejava.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。

+

读取文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+byte[] result = file.read();
+
+

写文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+file.write("Buession");
+file.write("Buession".getBytes());
+file.write("Buession", true); // 追加写
+
+

获取文件 MD5、SHA-1值

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+String md5 = file.getMd5(); // 获取文件 MD5
+String sha1 = file.getSha1(); // 获取文件 SHA-1
+
+

获取文件 MD5、SHA-1 值

+
import com.buession.io.file.File;
+import com.buession.io.MimeType;
+
+File file = new File("/tmp/debug.txt");
+
+MimeType result = file.getMimeType();
+
+

设置文件权限

+
import com.buession.io.file.Files;
+
+Files.chmod("/tmp/debug.txt", 0777);
+
+

设置文件用户组

+
import com.buession.io.file.Files;
+
+Files.chgrp("/tmp/debug.txt", "root");
+
+

设置文件用户

+
import com.buession.io.file.Files;
+
+Files.chown("/tmp/debug.txt", "root");
+
+

注解

+

注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。

+
import com.buession.io.json.annotation.MimeTypeString;
+
+class File {
+
+    @MimeTypeString
+    private MimeType mime;
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/jdbc/index.html b/manual/2.2/jdbc/index.html new file mode 100644 index 0000000..d76d82f --- /dev/null +++ b/manual/2.2/jdbc/index.html @@ -0,0 +1,28 @@ +buession-jdbc 参考手册-参考手册

buession-jdbc 参考手册

+

JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-jdbc</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通过提供的 API,您可以简化对 DBCP2DruidHikariTomcat 数据源的初始化,该类库基本不单独使用。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/json/index.html b/manual/2.2/json/index.html new file mode 100644 index 0000000..f0f4052 --- /dev/null +++ b/manual/2.2/json/index.html @@ -0,0 +1,63 @@ +buession-json 参考手册-参考手册

buession-json 参考手册

+

主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-json</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

封装了大量基于 jackson 的注解。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解说明
CalendarUnixTimestampjava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar
DateUnixTimestampjava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date
SqlDateUnixTimestampjava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date
TimestampUnixTimestampjava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp
JsonEnum2Map枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举
Sensitive通过该注解可以实现数据的脱敏
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/lang/index.html b/manual/2.2/lang/index.html new file mode 100644 index 0000000..92b28a9 --- /dev/null +++ b/manual/2.2/lang/index.html @@ -0,0 +1,27 @@ +buession-lang 参考手册-参考手册

buession-lang 参考手册

+

常用 POJO 类和枚举的定义,详细查看 API 参考手册。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-lang</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/net/index.html b/manual/2.2/net/index.html new file mode 100644 index 0000000..20ce857 --- /dev/null +++ b/manual/2.2/net/index.html @@ -0,0 +1,35 @@ +buession-net 参考手册-参考手册

buession-net 参考手册

+

网络相关工具类。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-net</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

IP 地址工具类

+

IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。

+
import com.buession.net.utils.InetAddressUtis;
+
+long result = InetAddressUtis.ip2long("127.0.0.1"); // 2130706433
+String ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1
+
+

URI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/redis/datasource.html b/manual/2.2/redis/datasource.html new file mode 100644 index 0000000..443a42c --- /dev/null +++ b/manual/2.2/redis/datasource.html @@ -0,0 +1,185 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

数据源

+

buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。 +通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。

+

数据源 DataSource 包括三个子接口:

+
    +
  • StandaloneDataSource:单机模式数据源
  • +
  • SentinelDataSource:哨兵模式数据源
  • +
  • ClusterDataSource:集群模式数据源
  • +
+

jedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。

+

在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。

+
<bean id="redisDataSource" class="com.buession.redis.client.connection.datasource.jedis.UserMapper"
+	p:host="${redis.host}"
+	p:port="${redis.port}"
+	p:password="${redis.password}" />
+
+

测试环境 properties:

+
redis.host=127.0.0.1
+redis.port=6379
+redis.password=
+
+

生产环境 properties:

+
redis.host=192.168.100.131
+redis.port=6379
+redis.password=passwd
+
+

连接池

+

通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfigConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。

+

连接池配置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项数据类型-- 默认值说明
lifobooleanGenericObjectPoolConfig.DEFAULT_LIFO池模式,为 true 时,后进先出;为 false 时,先进先出
fairnessbooleanGenericObjectPoolConfig.DEFAULT_FAIRNESS当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制
maxWaitDurationGenericObjectPoolConfig.DEFAULT_MAX_WAIT当连接池资源用尽后,调用者获取连接时的最大等待时间
minEvictableIdleTimeDuration60000连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除
softMinEvictableIdleTimeDurationGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数
evictionPolicyClassNameStringGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME驱逐策略的类名
evictorShutdownTimeoutDurationGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT关闭驱逐线程的超时时间
numTestsPerEvictionRunint-1检测空闲对象线程每次运行时检测的空闲对象的数量
testOnCreatebooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE在创建对象时检测对象是否有效,配置 true 会降低性能
testOnBorrowbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW在从对象池获取对象时是否检测对象有效,配置 true 会降低性能
testOnReturnbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能
testWhileIdlebooleantrue在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
blockWhenExhaustedbooleanGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
jmxEnabledbooleanGenericObjectPoolConfig.DEFAULT_JMX_ENABLE是否注册 JMX
jmxNamePrefixStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIXJMX 前缀
jmxNameBaseStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE使用 base + jmxNamePrefix + i 来生成 ObjectName
maxTotalintGenericObjectPoolConfig.DEFAULT_MAX_TOTAL最大连接数
minIdleintGenericObjectPoolConfig.DEFAULT_MIN_IDLE最小空闲连接数
maxIdleintGenericObjectPoolConfig.DEFAULT_MAX_IDLE最大空闲连接数
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/redis/index.html b/manual/2.2/redis/index.html new file mode 100644 index 0000000..514276c --- /dev/null +++ b/manual/2.2/redis/index.html @@ -0,0 +1,51 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-redis</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

介绍

+

buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。

+
import com.buession.redis.RedisTemplate;
+import com.buession.redis.core.Options;
+import com.buession.core.serializer.type.TypeReference;
+import java.utils.Map;
+import java.utils.HashMap;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.setOptions(new Options());
+redisTemplate.afterPropertiesSet();
+
+// 将 User 对象写进 key 为 user hash 中
+redisTemplate.hSet("user", "1", new User());
+
+// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User
+User user = redisTemplate.hGetObject("user", "1", User.class);
+
+// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User
+Map<String, User> data = redisTemplate.hGetAllObject("user", "1", new TypeReference<HashMap<String, User>>{});
+
+

展望

+

目前,buession-redis 仅支持 jedis,不支持 lettuce,我们计划在 2.3 ~ 2.5 的版本中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/redis/method.html b/manual/2.2/redis/method.html new file mode 100644 index 0000000..165b426 --- /dev/null +++ b/manual/2.2/redis/method.html @@ -0,0 +1,49 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

方法

+

buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。

+
import com.buession.redis.BaseRedisTemplate;
+
+BaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 删除哈希表 key 中的一个或多个指定域
+redisTemplate.hDel("user", "1", "2", "3");
+
+// 检查给定 key 是否存在
+redisTemplate.exists("user");
+
+// 获取列表 key 中,下标为 index 的元素
+redisTemplate.lIndex("user", 1);
+
+// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾
+redisTemplate.append("key", "value 1");
+
+

BaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。

+
import com.buession.redis.RedisTemplate;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类
+User user = redisTemplate.lIndexObject("user", 1, User.class);
+
+

序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/session/index.html b/manual/2.2/session/index.html new file mode 100644 index 0000000..82722d7 --- /dev/null +++ b/manual/2.2/session/index.html @@ -0,0 +1,28 @@ +buession-session 参考手册-参考手册

buession-session 参考手册

+

无文档

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-session</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块无实际意义,将在今后的版本中会删除掉。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/thesaurus/index.html b/manual/2.2/thesaurus/index.html new file mode 100644 index 0000000..c36becb --- /dev/null +++ b/manual/2.2/thesaurus/index.html @@ -0,0 +1,37 @@ +buession-thesaurus 参考手册-参考手册

buession-thesaurus 参考手册

+

对词库的解析,目前仅支持搜狗词条。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-thesaurus</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。

+
import com.buession.thesaurus.SogouParser;
+import com.buession.thesaurus.Parser;
+import com.buession.thesaurus.core.Word;
+import java.util.Set;
+
+Parser parser = new SogouParser();
+
+Set<Word> words parser.parse("搜谱拼音词条文件路径");
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/velocity/index.html b/manual/2.2/velocity/index.html new file mode 100644 index 0000000..c514867 --- /dev/null +++ b/manual/2.2/velocity/index.html @@ -0,0 +1,28 @@ +buession-velocity 参考手册-参考手册

buession-velocity 参考手册

+

spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-velocity</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/web/annotation.html b/manual/2.2/web/annotation.html new file mode 100644 index 0000000..47da268 --- /dev/null +++ b/manual/2.2/web/annotation.html @@ -0,0 +1,152 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

注解

+

我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解Request / Response作用域说明
@RequestClientIprequest方法参数获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress
@ContentTyperesponse类、方法设置响应 Content-Type
@HttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值
@DisableHttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存
@ResponseHeaderresponse类、方法设置响应头
@ResponseHeadersresponse类、方法批量设置响应头
@DocumentMetaDataresponse类、方法设置页面标题、页面编码、关键字、描述、版权等等元信息
+

获取用户端真实 IP

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

设置页面缓存

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

以上,会自动计算 Cache-Controlpragma 的值。当然,您也可以手动指定。

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5", cacheControl="public, max-age=5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.2/web/filter.html b/manual/2.2/web/filter.html new file mode 100644 index 0000000..d88d566 --- /dev/null +++ b/manual/2.2/web/filter.html @@ -0,0 +1,51 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

过滤器

+

我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。

+

servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。

+

过滤器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
过滤器说明
PoweredByFilterPowered By 过滤器
PrintUrlFilter打印当前请求 URL 过滤器
ResponseHeaderFilter响应头过滤器,设置响应头
ResponseHeadersFilter响应头过滤器,批量设置响应头
ServerInfoFilterServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点
+
\ No newline at end of file diff --git a/manual/2.2/web/index.html b/manual/2.2/web/index.html new file mode 100644 index 0000000..e472467 --- /dev/null +++ b/manual/2.2/web/index.html @@ -0,0 +1,28 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-web</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

buession-web 扩展了 spring-webmvcspring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.2/web/restful.html b/manual/2.2/web/restful.html new file mode 100644 index 0000000..832da6b --- /dev/null +++ b/manual/2.2/web/restful.html @@ -0,0 +1,47 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

RESTFUL

+

Restful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。

+

我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。

+
@RestController
+@RequestMapping(path = "/example")
+public class ExampleController extends AbstractRestController<Integer, ExampleDto, ExampleVo> {
+
+	@Override
+	public Response<ExampleVo> add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){
+		
+	}
+
+	@Override
+	public Response<ExampleVo> edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id, @RequestBody ExampleDto example){
+
+	}
+
+	@Override
+	public Response<ExampleVo> detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+
+	}
+
+	@Override
+	public Response<ExampleVo> delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.2/web/utils.html b/manual/2.2/web/utils.html new file mode 100644 index 0000000..b40b256 --- /dev/null +++ b/manual/2.2/web/utils.html @@ -0,0 +1,35 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

工具

+

我们封装了一些 web 相关的工具类,用于处理 request、response。

+

servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。

+

获取客户端真实 IP 地址:

+
RequestUtils.getClientIp(request);
+
+

我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。

+

优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr

+

是否是 Ajax 请求:

+
RequestUtils.isAjaxRequest(request);
+
+

是否是移动设备请求:

+
RequestUtils.isMobile(request);
+
+

设置缓存:

+
ResponseUtils.httpCache(response, 5); // 缓存 5 秒
+ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点
+
+
\ No newline at end of file diff --git a/manual/2.3/aop/index.html b/manual/2.3/aop/index.html new file mode 100644 index 0000000..3ce51bc --- /dev/null +++ b/manual/2.3/aop/index.html @@ -0,0 +1,27 @@ +buession-aop 参考手册-参考手册

buession-aop 参考手册

+

AOP 封装,方便实现自定义注解

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-aop</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/beans/index.html b/manual/2.3/beans/index.html new file mode 100644 index 0000000..fe4ef2f --- /dev/null +++ b/manual/2.3/beans/index.html @@ -0,0 +1,93 @@ +buession-beans 参考手册-参考手册

buession-beans 参考手册

+

该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-beans</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

属性拷贝

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.copyProperties(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.copyProperties(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

属性映射

+

使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。

+
import com.buession.beans.BeanUtils;
+
+BeanUtils.populate(target, source)
+
+
    +
  • 注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。
  • +
+

我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性

+
import com.buession.beans.BeanUtils;
+import org.springframework.cglib.core.Converter;
+
+BeanUtils.populate(target, source, new Converter() {
+
+	@Override
+	public Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){
+		if(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){
+			if(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){
+				return sourceFieldValue;
+			}
+		}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){
+			return sourceFieldValue;
+		}
+
+		return null;
+	}
+
+});
+
+

Bean 转换为 Map

+

使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Key

+
import com.buession.beans.BeanUtils;
+
+Map<String, Object> result = BeanUtils.toMap(bean)
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/builder.html b/manual/2.3/core/builder.html new file mode 100644 index 0000000..ccc3585 --- /dev/null +++ b/manual/2.3/core/builder.html @@ -0,0 +1,140 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

Map、集合的便捷式构建,减少您的代码行数。

+

您需要往 Map、List 中添加元素的传统写法是:

+
import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+List<String> list = new ArrayList<>();
+list.add("A");
+list.add("B");
+list.add("C");
+
+Map<String, Object> map = new HashMap<>();
+map.put("a", "A");
+map.put("b", "B");
+map.put("c", "C");
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.Map;
+
+List<String> list = ListBuilder.<String>create().add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create().put("a", "A").put("b", "B").put("c", "C");
+
+

此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。

+
import com.buession.core.builder.ListBuilder;
+import com.buession.core.builder.MapBuilder;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+List<String> list = ListBuilder.<String>create(LinkedList.class).add("A").add("B").add("C").build();
+
+Map<String, Object> map = MapBuilder.<String, Object>create(LinkedHashMap.class).put("a", "A").put("b", "B").put("c", "C");
+
+
    +
  • 注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数
  • +
+

当您有 value 为 null 时,不添加到 List 时,传统写法:

+
import java.util.ArrayList;
+import java.util.List;
+
+String value = null;
+List<String> list = new ArrayList<>();
+
+if(value != null){
+	list.add(value);
+}
+
+

而当您使用 buession framework 可以这么写:

+
import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+String value = null;
+List<String> list = ListBuilder.<String>create().addIfPresent(value).build();
+
+

Map、Set、Queue 同理。

+

便捷方法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
List ListBuilder.epmty()创建空的 V 类型的 List 对象
List ListBuilder.of()创建空的 V 类型的 List 对象
List ListBuilder.of(V value)创建仅有一个元素的 V 类型的 List 对象
Queue QueueBuilder.epmty()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of()创建空的 V 类型的 Queue 对象
Queue QueueBuilder.of(V value)创建仅有一个元素的 V 类型的 Queue 对象
Set SetBuilder.epmty()创建空的 V 类型的 Set 对象
Set SetBuilder.of()创建空的 V 类型的 Set 对象
Set SetBuilder.of(V value)创建仅有一个元素的 V 类型的 Set 对象
<K, V> Map<K, V> MapBuilder.epmty()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of()创建空的 Key 为 K 类型,值为 V 类型的 Map 对象
<K, V> Map<K, V> MapBuilder.of(V value)创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象
+

empty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/codec.html b/manual/2.3/core/codec.html new file mode 100644 index 0000000..fb5cbf9 --- /dev/null +++ b/manual/2.3/core/codec.html @@ -0,0 +1,82 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。

+

我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。

+

此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。

+

此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。

+
USER_NOT_FOUND.code = 10404
+USER_NOT_FOUND.message = 用户不存在
+
+USER_LOGIN_FAILURE.code = 10405
+USER_LOGIN_FAILURE.message = 登录失败
+
+
<context:property-placeholder location="classpath:error_message.properties"/>
+
+<util:properties location="classpath:error_message.properties" local-override="true"/>
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message("USER_NOT_FOUND")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。

+
USER_NOT_FOUND.errorCode = 10404
+USER_NOT_FOUND.errorMessage = 用户不存在
+
+USER_LOGIN_FAILURE.errorCode = 10405
+USER_LOGIN_FAILURE.errorMessage = 登录失败
+
+
import com.buession.core.codec.Message;
+import com.buession.core.codec.MessageObject;
+
+public UserServiceImpl implements UserService {
+
+	@Message(value = "USER_NOT_FOUND", code = "errorCode", message = "errorMessage")
+	private MessageObject userNotFound;
+
+	@Override
+	public User update(User user) throws Exception{
+		User dbUser = get(user.getId());
+
+		if(dbUser == null){
+			throw new Exception(userNotFound.getMessage() + "(code: " + userNotFound.getCode() + ")");
+			// 用户不存在(code: 10404)
+		}
+
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/collect.html b/manual/2.3/core/collect.html new file mode 100644 index 0000000..1c923c7 --- /dev/null +++ b/manual/2.3/core/collect.html @@ -0,0 +1,160 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

收集器

+

数组、Map、集合的工具类

+

数组

+

数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 ListSet 以及字符串类型的数组、数组合并、数组元素操作等方法。

+

检测数组 array 中是否存在元素 element:

+
import com.buession.core.collect.Arrays;
+
+boolean result = Arrays.contains(array, element);
+
+

返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.indexOf(array, element);
+
+

返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:

+
import com.buession.core.collect.Arrays;
+
+int result = Arrays.lastIndexOf(array, element);
+
+

将字符串拼接会字符串:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String result = Arrays.toString(array);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String glue = "-";
+String result = Arrays.toString(array, glue);
+// 1-2-3
+
+

可以通过方法 toList、toSet 转换为 List 和 Set:

+
import com.buession.core.collect.Arrays;
+import java.util.List;
+import java.util.Set;
+
+int[] array = {1, 2, 3};
+List<Integer> list = Arrays.toList(array);
+Set<Integer> set = Arrays.toSet(array);
+
+

将数组转换为字符串类型的数组:

+
import com.buession.core.collect.Arrays;
+
+int[] array = {1, 2, 3};
+String[] result = Arrays.toStringArray(array);
+
+

将数组进行合并:

+
import com.buession.core.collect.Arrays;
+
+String[] result = Arrays.toStringArray(array1, array2, array3);
+
+

对数组元素进行操作:

+
import com.buession.core.collect.Arrays;
+
+String[] array = {"A", "B", "C"};
+String[] result = Arrays.map(array, String.class, fn);
+
+

第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现

+

Lists

+

List 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Lists.toString(list);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Lists.toString(list);
+// 1-2-3
+
+

可以通过方法 toSet 将 List 转换为 Set:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+List<Integer> list = ListBuilder.<Integer>create().add(1).add(2).add(3).build();
+Set<Integer> set = Lists.toSet(list);
+
+

Sets

+

Sett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。

+

将字符串拼接会字符串:

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String result = Sets.toString(set);
+// 1, 2, 3
+
+

可以通过参数 glue 指定连接符,默认为:", "

+
import com.buession.core.collect.Sets;
+import com.buession.core.builder.SetBuilder;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+String glue = "-";
+String result = Sets.toString(list);
+// 1-2-3
+
+

可以通过方法 toList 将 Set 转换为 List:

+
import com.buession.core.collect.Lists;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+import java.util.Set;
+
+Set<Integer> set = SetBuilder.<Integer>create().add(1).add(2).add(3).build();
+List<Integer> list = Sets.toList(set);
+
+

Maps

+

Map 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。

+

对 Map 进行操作:

+
import com.buession.core.collect.Maps;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> maps = new HashMap<>();
+Map<String, String> result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());
+
+

第二个、第三参数为 java.util.function.Function 的实现

+

可以通过方法 toList 将 Map 的 value 转换为 List:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.List;
+
+List<T> list = Maps.toList(maps);
+
+

可以通过方法 toSet 将 Map 的 value 转换为 Set:

+
import com.buession.core.collect.Maps;
+import com.buession.core.builder.ListBuilder;
+import java.util.Set;
+
+Set<T> set = Maps.toSet(maps);
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/configurer.html b/manual/2.3/core/configurer.html new file mode 100644 index 0000000..e371789 --- /dev/null +++ b/manual/2.3/core/configurer.html @@ -0,0 +1,46 @@ +参考手册

Configurer# buession-core 参考手册

+

配置器

+

使用配置参数对对象进行配置。

+

接口规范。

+
@FunctionalInterface
+public interface Configurer<T, C> {
+
+	/**
+	 * 使用配置参数 config 对对象 object 进行配置
+	 *
+	 * @param object
+	 * 		配置对象
+	 * @param config
+	 * 		配置参数
+	 */
+	void configure(T object, C config);
+
+}
+
+

示例:

+
public class DefaultConfigurer implements Configurer<User, Map<String, Object>> {
+
+	@Override
+	public void configure(final User user, final Map<String, Object> configs) {
+		user.setUsername(configs.get("name"));
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/context.html b/manual/2.3/core/context.html new file mode 100644 index 0000000..b368dc1 --- /dev/null +++ b/manual/2.3/core/context.html @@ -0,0 +1,91 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

上下文

+

注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。

+

在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:

+
    +
  • 逻辑少
  • +
  • 与 Dao 层进行交互,多个 Dao 层的复用
  • +
  • Service 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层
  • +
+
import com.buession.core.context.stereotype.Manager;
+import org.springframework.stereotype.Service;
+
+public interface UserManager {
+
+	User getByPrimary(int id);
+
+}
+
+@Manager
+public class UserManagerImpl implements UserManager {
+
+	@Autowired
+	private UserDao userDao;
+
+	@Autowired
+	private UserProfileDao userProfileDao;
+
+	@Autowired
+	private RedisTemplate redisTemplate;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = redisTemplate.hGetObject("user", Integer.toString(id), User.class);
+
+		if(user == null){
+			user = userDao.getByPrimary(id);
+			if(user != null){
+				user.setProfile(userProfileDao.getByUserId(id));
+				redisTemplate.hSet("user", Integer.toString(id), user);
+			}else{
+				throw new RuntimeException("用户不存在");
+			}
+		}
+
+		return user;
+	}
+
+}
+
+public interface UserService {
+
+	User getByPrimary(int id);
+
+}
+
+@Service
+public class UserServiceImpl implements UserService {
+
+	@Autowired
+	private UserManager userManager;
+
+
+	@Override
+	public User getByPrimary(int id){
+		User user = userManager.getByPrimary(id);
+
+		...
+
+		return user;
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/converter.html b/manual/2.3/core/converter.html new file mode 100644 index 0000000..e3bba92 --- /dev/null +++ b/manual/2.3/core/converter.html @@ -0,0 +1,159 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

接口定义:

+
@FunctionalInterface
+public interface Converter<S, T> {
+
+	T convert(final S source);
+
+}
+
+

将类似为 S 的对象转换为类型为 T 的对象。

+

内置转换器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
转换器说明
ArrayConverter<S, T>将 S 类型的数组转换为 T 类型的数组
EnumConverter<E extends Enum>枚举转换器,将字符串转换为枚举 E
BinaryEnumConverter<E extends Enum>枚举转换器,将 byte 数组转换为枚举 E
BooleanStatusConverter将布尔值转换为 com.buession.lang.Status
StatusBooleanConvertercom.buession.lang.Status 转换为布尔值
PredicateStatusConverter通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status
ListConverter<S, T>将 S 类型的 List 对象转换为 T 类型的 List 对象
SetConverter<S, T>将 S 类型的 Set 对象转换为 T 类型的 Set 对象
MapConverter<SK, SV, TK, TV>将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map
+

将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象

+
import com.buession.core.converter.MapConverter;
+import java.util.Map;
+
+Map<Integer, Object> source;
+Map<String, String> target;
+MapConverter<Integer, Object, String, String> converter = new MapConverter<>();
+
+target = converter.convert(source);
+
+

将字符串转换为枚举

+
import com.buession.core.converter.EnumConverter;
+import com.buession.lang.Gender;
+
+Gender target;
+EnumConverter<Gender> converter = new EnumConverter<>(Gender.class);
+
+target = converter.convert("FEMALE");
+// Gender.FEMALE
+
+target = converter.convert("F");
+// null
+
+

POJO 类映射

+

我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。

+
public interface Mapper<S, T> {
+
+	/**
+	 * 将源对象映射到目标对象
+	 *
+	 * @param object
+	 * 		源对象
+	 *
+	 * @return 目标对象实例
+	 */
+	T mapping(S object);
+
+	/**
+	 * 将源对象数组映射到目标对象数组
+	 *
+	 * @param object
+	 * 		源对象数组
+	 *
+	 * @return 目标对象实例数组
+	 */
+	T[] mapping(S[] object);
+
+	/**
+	 * 将源 list 对象映射到目标 list 对象
+	 *
+	 * @param object
+	 * 		源 list 对象
+	 *
+	 * @return 目标对象 list 实例
+	 */
+	List<T> mapping(List<S> object);
+
+	/**
+	 * 将源 set 对象映射到目标 set 对象
+	 *
+	 * @param object
+	 * 		源 set 对象
+	 *
+	 * @return 目标对象 set 实例
+	 */
+	Set<T> mapping(Set<S> object);
+
+}
+
+

我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。

+
import com.buession.core.converter.mapper.PropertyMapper;
+
+User source = new User();
+User target = new User();
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
+propertyMapper.form(source::getId).to(target:setId)
+// null
+
+PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();
+propertyMapper.form(source::getUsername).to(target:setUsername)
+// null
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/customizer.html b/manual/2.3/core/customizer.html new file mode 100644 index 0000000..bd0429c --- /dev/null +++ b/manual/2.3/core/customizer.html @@ -0,0 +1,46 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

定制器

+

使用源对象对目标对象进行定制。

+

接口规范。

+
@FunctionalInterface
+public interface Customizer<S, T> {
+
+	/**
+	 * 定制
+	 *
+	 * @param source
+	 * 		源实例
+	 * @param target
+	 * 		待定制实例
+	 */
+	void customize(S source, T target);
+
+}
+
+

示例:

+
public class DefaultCustomizer implements Customizer<UserModel, UserVo> {
+
+	@Override
+	public void customize(final UserModel userModel, final UserVo userVo) {
+		userVo.setUsername(userModel.getUsername());
+	}
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/datetime.html b/manual/2.3/core/datetime.html new file mode 100644 index 0000000..285d67c --- /dev/null +++ b/manual/2.3/core/datetime.html @@ -0,0 +1,33 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

日期时间

+

日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。

+

获取当前 Unix 时间戳(秒):

+
import com.buession.core.datetime.DateTime;
+
+DateTime.unixtime();
+
+

该方法我们在实际业务中经常用到。

+

以 "msec sec" 的格式返回当前 Unix 时间戳和微秒数:

+
import com.buession.core.datetime.DateTime;
+
+DateTime.microtime();
+// 1657171717 948000
+
+

该方法参考 PHP 的 microtime 函数而来。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/deserializer.html b/manual/2.3/core/deserializer.html new file mode 100644 index 0000000..d3c86ea --- /dev/null +++ b/manual/2.3/core/deserializer.html @@ -0,0 +1,54 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

将二进制或 JSON 反序列化为对象。

+

您可以通过该 API,实现将将二进制、JSON 字符串反序列化为对象。

+

序列化、反序列化类

+ + + + + + + + + + + + + + + + + + + + + + + + + +
说明
DefaultByteArrayDeserializer将对象序列化为二进制,或将二进制反序列化为对象
FastJsonJsonDeserializer基于 FastJSON 的对象与 JSON 之间的序列化和反序列化
GsonJsonDeserializer基于 Gson 的对象与 JSON 之间的序列化和反序列化
JacksonJsonDeserializer基于 Jackson2 的对象与 JSON 之间的序列化和反序列化
+
    +
  1. DefaultByteArrayDeserializer 通过 URLDecoder.decode 进行解码成 byte 数组,再反序列化为对象
  2. +
  3. 在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑
  4. +
  5. FastJsonJsonDeserializerGsonJsonDeserializerJacksonJsonDeserializer 可以通过参数 Class<T>TypeReference<V> 指定返回的对象类型
  6. +
  7. com.buession.core.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回
  8. +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/exception.html b/manual/2.3/core/exception.html new file mode 100644 index 0000000..d995d50 --- /dev/null +++ b/manual/2.3/core/exception.html @@ -0,0 +1,70 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

异常

+

通用异常的定义。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
异常说明
AccessException拒绝访问异常
ClassInstantiationException类实例化异常
ConversionException数据类型转换异常
DataAlreadyExistException数据已存在异常
DataNotFoundException数据不存在或未找到异常
InsteadException类方法废弃后,需要使用其它类库方法来替代
NestedRuntimeException嵌套运行时异常
OperationException运算异常
PresentException--
SerializationException序列化异常
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/id.html b/manual/2.3/core/id.html new file mode 100644 index 0000000..44c233a --- /dev/null +++ b/manual/2.3/core/id.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。

+

接口规范。

+
public interface IdGenerator<T> {
+
+	/**
+	 * 获取下一个 ID
+	 *
+	 * @return ID
+	 */
+	T nextId();
+
+}
+
+

ID 生成器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
生成器说明
AtomicSimpleIdGenerator基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
AtomicUUIDIdGenerator基于 AtomicLong 递增的,UUID ID 生成器
NanoIDIdGeneratorjnanoid ID 生成器,可通过构造函数指定字符范围、长度
RandomDigitIdGenerator随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定
RandomIdGenerator随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位
SimpleIdGenerator简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 "-" 过滤掉
SnowflakeIdGenerator雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值
UUIDIdGeneratorUUID ID 生成器
+
import com.buession.core.id.AtomicUUIDIdGenerator;
+import com.buession.core.id.NanoIDIdGenerator;
+import com.buession.core.id.SnowflakeIdGenerator;
+import com.buession.core.id.UUIDIdGenerator;
+import com.buession.core.id.SimpleIdGenerator;
+
+AtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000001
+idGenerator.nextId(); // 00000000-0000-0000-0000-000000000002
+
+NanoIDIdGenerator idGenerator = new NanoIDIdGenerator();
+idGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK
+
+SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();
+idGenerator.nextId(); // 170602258864545792
+
+UUIDIdGenerator idGenerator = new UUIDIdGenerator();
+idGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4
+
+SimpleIdGenerator idGenerator = new SimpleIdGenerator();
+idGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05
+
+
    +
  • 注:关于雪花算法、jnanoid 算法的可自行搜索。
  • +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/index.html b/manual/2.3/core/index.html new file mode 100644 index 0000000..1c90c23 --- /dev/null +++ b/manual/2.3/core/index.html @@ -0,0 +1,59 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-core</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

构建器

+

Map、集合的便捷式构建,减少您的代码行数

+

编码器

+

目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中

+

收集器

+

数组、Map、集合的工具类

+

上下文

+

定义应用上下文的类库、注解

+

配置器接

+

使用配置参数对对象进行配置

+

定制器

+

使用源对象对目标对象进行定制

+

转换器

+

数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。

+

日期时间

+

日期、时间工具

+

ID 生成器

+

基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。

+

数学函数

+

定义了实用的数学函数

+

序列化

+

将对象序列化为二进制或 JSON。

+

反序列化

+

将二进制或 JSON 反序列化为对象。

+

验证器

+

数据验证器及其注解

+

工具类

+

常用通用性工具类

+

其它

+

通用的接口定义,框架自身类

+

异常

+

通用异常的定义

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/math.html b/manual/2.3/core/math.html new file mode 100644 index 0000000..90e32cf --- /dev/null +++ b/manual/2.3/core/math.html @@ -0,0 +1,67 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

数学函数

+

定义了实用的数学函数。

+ + + + + + + + + + + + + + + + + +
方法说明
continuousSum计算两个数之间连续相加之和
rangeValue获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身
+
import com.buession.core.math.Math;
+
+long result = Math.continuousSum(1, 100);
+// 5050
+
+
import com.buession.core.math.Math;
+
+long value = 3;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 4
+
+
import com.buession.core.math.Math;
+
+long value = 5;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 5
+
+
import com.buession.core.math.Math;
+
+long value = 11;
+long min = 4;
+long max = 10;
+long result = Math.rangeValue(value, min, max);
+// 10
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/other.html b/manual/2.3/core/other.html new file mode 100644 index 0000000..05a2423 --- /dev/null +++ b/manual/2.3/core/other.html @@ -0,0 +1,101 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

其它

+

通用的接口定义,框架自身类,以及其它杂项。

+

框架自身工具

+

获取 Buession Framework 版本:

+
import com.buession.core.Framework;
+import com.buession.core.BuesssionFrameworkVersion;
+
+BuesssionFrameworkVersion.getVersion(); // 2.3.0
+Framework.VERSION; // 2.3.0
+
+

获取 Buession Framework 框架名称:

+
import com.buession.core.Framework;
+
+Framework.NAME; // "Buession"
+
+

命令执行器

+

命令执行器接口:

+
/**
+ * 命令执行器
+ *
+ * @param <C>
+ * 		命令上下文
+ * @param <R>
+ * 		命令执行返回值
+ */
+@FunctionalInterface
+public interface Executor<C, R> {
+
+	/**
+	 * 命令执行
+	 *
+	 * @param context
+	 * 		命令执行器上下文
+	 *
+	 * @return 命令执行返回值,R 类型的实例
+	 */
+	R execute(C context);
+
+}
+
+

您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。

+

销毁接口

+

功能类似 java.io.Closeable

+
public interface Destroyable {
+
+	/**
+	 * 销毁相关资源
+	 *
+	 * @throws IOException
+	 * 		IO 错误时抛出
+	 */
+	void destroy() throws IOException;
+
+}
+
+

Rawable

+

原始的,约定实现该接口的类,必须返回原始字节数组。

+
public interface Rawable {
+
+	/**
+	 * 返回原始的字节数组
+	 *
+	 * @return 原始的字节数组
+	 */
+	byte[] getRaw();
+
+}
+
+

名称节点

+

名称节点,约定实现该接口的类应该返回一个名称

+
public interface NamedNode {
+
+	/**
+	 * 返回节点名称
+	 *
+	 * @return 节点名称
+	 */
+	@Nullable
+	String getName();
+
+}
+
+

分页

+

com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。

+
\ No newline at end of file diff --git a/manual/2.3/core/serializer.html b/manual/2.3/core/serializer.html new file mode 100644 index 0000000..2d43b68 --- /dev/null +++ b/manual/2.3/core/serializer.html @@ -0,0 +1,52 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

构建器

+

将对象序列化为二进制或 JSON。

+

您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串。

+

序列化、反序列化类

+ + + + + + + + + + + + + + + + + + + + + + + + + +
说明
DefaultByteArraySerializer将对象序列化为二进制
FastJsonJsonSerializer基于 FastJSON 的对象与 JSON 之间的序列化
GsonJsonSerializer基于 Gson 的对象与 JSON 之间的序列化
JacksonJsonSerializer基于 Jackson2 的对象与 JSON 之间的序列化
+
    +
  1. DefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码
  2. +
  3. FastJsonJsonSerializerGsonJsonSerializerJacksonJsonSerializer 可以通过参数 Class<T>TypeReference<V> 指定返回的对象类型
  4. +
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/utils.html b/manual/2.3/core/utils.html new file mode 100644 index 0000000..58efa3c --- /dev/null +++ b/manual/2.3/core/utils.html @@ -0,0 +1,199 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

工具类

+

常用通用性工具类。

+

Byte 数组比较

+

ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。

+

注解工具

+

AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class<?> clazz, final Class<? extends Annotation>[] annotations)hasMethodAnnotationPresent(Method method, final Class<? extends Annotation>[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。

+

断言

+

Assert 和 springframework 中的注解类似,不过逻辑有些相反。

+

Byte 工具

+

ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] bytes;
+char c = ByteUtils.toChar(bytes);
+
+char[] chars = ByteUtils.toChar(bytes);
+
+

byte 数组连接。

+
import com.buession.core.utils.ByteUtils;
+
+byte[] dest;
+byte[] source
+byte[] result = ByteUtils.concat(dest, source);
+
+

Character 工具

+

CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。

+
import com.buession.core.utils.CharacterUtils;
+
+char c;
+byte[] bytes = ByteUtils.toBytes(c);
+
+char[] chars;
+byte[] bytes = ByteUtils.toBytes(chars);
+
+

数字工具

+

NumberUtils 提供了对数字相关的操作。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
int2bytes将 int 转换为 byte[]
bytes2int将 byte[] 转换为 int
long2bytes将 long 转换为 byte[]
bytes2long将 byte[] 转换为 long
float2bytes将 float 转换为 byte[]
bytes2float将 byte[] 转换为 float
double2bytes将 double 转换为 byte[]
bytes2double将 byte[] 转换为 double
+

字符串工具

+

StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。

+

截取字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.substr("abcde", 1); // bcde
+String result = StringUtils.substr("abcde", 1, 2); // bc
+
+

生成随机字符串

+
import com.buession.core.utils.StringUtils;
+
+String result = StringUtils.random(length);
+
+

比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equals("abcd", "abce", 3); // true
+boolean result = StringUtils.equals("abcd", "abce", 4); // false
+
+

忽略大小写比较两个 CharSequence 前 length 位是否相等

+
import com.buession.core.utils.StringUtils;
+
+boolean result = StringUtils.equalsIgnoreCase("abcd", "Abce", 3); // true
+boolean result = StringUtils.equalsIgnoreCase("abcd", "aBce", 4); // false
+
+

拼音工具

+

PinyinUtils 封装了获取中文拼音、拼音首字母的方法。

+
import com.buession.core.utils.PinyinUtils;
+
+String result = PinyinUtils.getPinyin("中国"); // zhongguo
+String result = PinyinUtils.getPinYinFirstChar("中国"); // zg
+
+

随机数工具

+

RandomUtils 封装了随机数的生成。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法说明
nextBoolean随机布尔值
nextBytes随机字节数组
nextInt生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE
nextLong生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE
nextFloat生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE
nextDouble生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE
+

Properties 工具

+

PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。

+
import com.buession.core.utils.SystemPropertyUtils;
+
+Integer result = PropertiesUtils.getInteger(properties, key);
+Boolean result = PropertiesUtils.getBoolean(properties, key);
+
+

System Property 工具

+

SystemPropertyUtils 封装了系统属性或系统环境变量的操作。

+

设置属性方法 setPropertySystem.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty

+
import com.buession.core.utils.SystemPropertyUtils;
+
+SystemPropertyUtils.setProperty("http.port", 8080);
+SystemPropertyUtils.setProperty("http.ssl.enable", false);
+
+

获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。

+
String value = System.getProperty(name);
+
+if(Validate.hasText(value) == false){
+  value = System.getenv(name);
+}
+
+

版本工具

+

VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。

+
import com.buession.core.utils.VersionUtils;
+
+VersionUtils.compare("1.0.0", "1.0.1-beta"); // -1
+VersionUtils.compare("1.0.0", "1.0.0r"); // -1
+
+

规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本

+

获取类的版本值

+
import com.buession.core.utils.VersionUtils;
+
+ByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.3.0
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/core/validator.html b/manual/2.3/core/validator.html new file mode 100644 index 0000000..9a9789d --- /dev/null +++ b/manual/2.3/core/validator.html @@ -0,0 +1,239 @@ +buession-core 参考手册-参考手册

buession-core 参考手册

+

验证器

+

数据验证器及其注解。

+

该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。

+

并提供对应的基于 javax.validation 的校验注解。

+

验证是否为 null

+

判断任意对象是否为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNull(obj);
+
+

验证是否不为 null

+

判断任意对象是否不为 null

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isNotNull(obj);
+
+

判断字符串是否为空白字符串

+

判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 false

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isBlank(str); // true
+
+String str = "";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\n";
+boolean result = Validate.isBlank(str); // true
+
+String str = "\\r\\na";
+boolean result = Validate.isBlank(str); // false
+
+
    +
  • 注:isNotBlank 与之相反
  • +
+

判断是否为空

+

isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空

+
import com.buession.core.validator.Validate;
+
+String str = null;
+boolean result = Validate.isEmpty(str); // true
+
+String str = " ";
+boolean result = Validate.isEmpty(str); // false
+
+boolean result = Validate.isEmpty(new String[]{}); // true
+
+boolean result = Validate.isEmpty(new Integer[]{1}); // false
+
+
    +
  • 注:isNotEmpty 与之相反
  • +
+

判断是否在两个数之间

+

isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3); // true
+
+boolean result = Validate.isBetween(2, 2, 3); // false
+
+

可通过参数设置是否包含边界值

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isBetween(2, 1, 3, true); // true
+
+boolean result = Validate.isBetween(2, 2, 3, true); // true
+
+

判断是否为电话号码

+

isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isTel("028-12345678"); // true
+
+boolean result = Validate.isTel("028-02345678"); // false
+
+

判断是否为手机号码

+

isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isMobile("028-12345678"); // false
+
+boolean result = Validate.isMobile("13800138000"); // true
+
+

判断是否为邮政编码

+

isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isPostCode("043104"); // false
+
+boolean result = Validate.isPostCode("643104"); // true
+
+

判断是否为 QQ 号码

+

isQQ 可判断一个字符串是否为 QQ 号码。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isQQ("043104"); // false
+
+boolean result = Validate.isQQ("25132.141"); // true
+
+

判断是否为身份证号码

+

isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx");
+
+

可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。

+
import com.buession.core.validator.Validate;
+
+boolean result = Validate.isIDCard("xxxxxxxxxxxxxxxxx", true, "2.10-01-01");
+
+

其它,更多方法可以参考手册

+

注解

+

javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解验证的数据类型说明
@AlnumCharSequence 的子类型,Character验证注解的元素值是否为数字
@AlphaCharSequence 的子类型,Character验证注解的元素值是否为数字
@NumericCharSequence 的子类型,Character验证注解的元素值是否为数字
@Betweenshort、int、double 等任何 Number 的子类型验证注解的元素值是否为在两个数之间
@EmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否为空
@NotEmptyCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组验证注解的元素值是否不为空
@HasTextCharSequence 的子类型验证注解的元素值是否有非空字符
@IDCardCharSequence 的子类型验证注解的元素值是否有非空字符
@IpCharSequence 的子类型验证注解的元素值是否有非空字符
@IsbnCharSequence 的子类型验证注解的元素值是否有非空字符
@MimeTypeCharSequence 的子类型验证注解的元素值是否有非空字符
@MobileCharSequence 的子类型验证注解的元素值是否有非空字符
@Null任意类型验证注解的元素值是否为 null
@NotNull任意类型验证注解的元素值是否为 null
@PortInteger验证注解的元素值是否为 null
@PostCodeCharSequence 的子类型验证注解的元素值是否为 null
@QQCharSequence 的子类型验证注解的元素值是否为 null
@TelCharSequence 的子类型验证注解的元素值是否为 null
@XdigitCharSequence 的子类型验证注解的元素值是否为 null
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/cron/index.html b/manual/2.3/cron/index.html new file mode 100644 index 0000000..8a125c0 --- /dev/null +++ b/manual/2.3/cron/index.html @@ -0,0 +1,30 @@ +buession-cron 参考手册-参考手册

buession-cron 参考手册

+

对 quartz 的二次封装

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-cron</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。

+

由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。

+

且在今后的版本中,该模块可能会被废弃。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/dao/index.html b/manual/2.3/dao/index.html new file mode 100644 index 0000000..3378749 --- /dev/null +++ b/manual/2.3/dao/index.html @@ -0,0 +1,46 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-dao</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。

+

同时,我们在代码层面实现了数据库的读写分离。

+

我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。

+

Dao 接口

+

接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.3.0/com/buession/dao/Dao.html

+
public interface Dao<P, E> {
+}
+
+
    +
  • P:主键类型
  • +
  • E:实体类
  • +
+

分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset

+

条件为 Map<String, Object> 类型,允许为 null。

+

排序为 Map<String, com.buession.lang.Order> 类型,允许为 null。

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/dao/mongodb.html b/manual/2.3/dao/mongodb.html new file mode 100644 index 0000000..71917bf --- /dev/null +++ b/manual/2.3/dao/mongodb.html @@ -0,0 +1,24 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MongoDB

+

Buession Framework 扩展 spring-data-mongodb 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplateslaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

AbstractMongoDBDaoreplace 执行的也是 insert。 +在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/dao/mybatis.html b/manual/2.3/dao/mybatis.html new file mode 100644 index 0000000..a5963f1 --- /dev/null +++ b/manual/2.3/dao/mybatis.html @@ -0,0 +1,170 @@ +buession-dao 参考手册-参考手册

buession-dao 参考手册

+

MyBatis

+

Buession Framework 扩展 MyBatis 的文档。

+

读写分离

+

要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplateslaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。

+

Mybatis 约定

+
    +
  1. 如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace
  2. +
+
namespace com.buession.dao.test.dao;
+
+public class UserDaoImpl extends AbstractMyBatisDao<Integer, User> {
+
+	@Override
+	protected String getStatement(){
+		return "com.buession.dao.test.dao.UserMapper";
+	}
+
+}
+
+
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.buession.dao.test.dao.UserMapper">
+</mapper>
+
+
    +
  1. Mapper 的 SQL ID 和方法名保持一致
  2. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQL ID说明返回值
insert插入数据影响的行数
batchInsert批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入每次插入影响的行数列表
replace替换数据,即:REPLACE 语句影响的行数
batchReplace批量替换数据,即:REPLACE 语句每次替换数据影响的行数列表
update更新数据更新条数
updateByPrimary根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值更新条数
getByPrimary根据主键查询数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
selectOne(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据一条数据结果
select查询数据数据结果列表
getAll查询所有数据数据结果列表
count获取记录数记录数
deleteByPrimary根据主键删除数据影响条数
delete删除数据影响条数
clear清除数据影响条数
truncate截断数据影响条数
+
    +
  • 注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。 +以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。
  • +
+

Mybatis 类型处理器

+

MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeHandler说明
DefaultEnumTypeHandler默认 Enum 类型处理器,将值直接转换为枚举字段
IgnoreCaseEnumTypeHandler忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段
DefaultJsonTypeHandlerJSON 处理器,将 JSON 格式的字符串值和类型 <E> 进行转换
DefaultSetEnumTypeHandler默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素
IgnoreCaseSetEnumTypeHandler忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素
DefaultSetTypeHandler默认 Set 类型处理器,将值以 "," 拆分转换为 Set<String>
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/geoip/index.html b/manual/2.3/geoip/index.html new file mode 100644 index 0000000..f511749 --- /dev/null +++ b/manual/2.3/geoip/index.html @@ -0,0 +1,79 @@ +buession-geoip 参考手册-参考手册

buession-geoip 参考手册

+

对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-geoip</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。

+

geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。

+

获取国家信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Country country = resolver.country("114.114.114.114");
+// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}
+
+Country country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8
+// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}
+
+

获取城市信息

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+District district = resolver.district("114.114.114.114");
+// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}
+
+District district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8
+// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}
+
+

获取位置信息

+

位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。

+
import com.buession.geoip.model.Country;
+import com.buession.geoip.model.DatabaseResolver;
+
+DatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream("/maxmind/City.mmdb"));
+Location location = resolver.location("114.114.114.114");
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}
+
+Location location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8
+// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}
+
+

缓存

+

为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。

+

Resolver 的 Spring Factory Bean

+

我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。

+
<bean id="geoIPResolver" class="com.buession.geoip.spring.GeoIPResolverFactoryBean"
+  p:dbPath="/data/maxmind/City.mmdb"
+  p:stream-ref="dbStream"
+  p:enableCache="true/false"
+ />
+
+
    +
  1. dbPathstream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。
  2. +
  3. enableCache 可以控制是否缓存。
  4. +
+

关于 IP 库

+

buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/git/index.html b/manual/2.3/git/index.html new file mode 100644 index 0000000..3c9e788 --- /dev/null +++ b/manual/2.3/git/index.html @@ -0,0 +1,27 @@ +buession-git 参考手册-参考手册

buession-git 参考手册

+

获取项目 GIT 信息。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-git</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/httpclient/configuration.html b/manual/2.3/httpclient/configuration.html new file mode 100644 index 0000000..237c4db --- /dev/null +++ b/manual/2.3/httpclient/configuration.html @@ -0,0 +1,139 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接配置

+

您可以通过连接配置类 Configuration 配置 apache httpcomponentsokhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponentsokhttp3 的配置信息。

+

配置属性说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名称数据类型apache httpcomponents 对应配置okhttp3 对应配置默认值说明
maxConnectionsintmaxTotalmaxIdleConnections5000最大连接数
maxPerRouteintdefaultMaxPerRoute--500每个路由的最大连接数
idleConnectionTimeintcloseIdleConnectionskeepAliveDuration60000空闲连接存活时长(单位:毫秒)
connectTimeoutintconnectTimeoutconnectTimeout3000连接超时时间(单位:毫秒)
connectionRequestTimeoutintconnectionRequestTimeout--5000从连接池获取连接的超时时间(单位:毫秒)
readTimeoutintsocketTimeoutreadTimeout5000读取超时时间(单位:毫秒)
allowRedirectsBooleanredirectsEnabledfollowRedirects--是否允许重定向
relativeRedirectsAllowedBooleanrelativeRedirectsAllowed----是否应拒绝相对重定向
circularRedirectsAllowedBooleancircularRedirectsAllowed----是否允许循环重定向
maxRedirectsIntegermaxRedirects----最大允许重定向次数
authenticationEnabledbooleanauthenticationEnabled----是否开启 Http Basic 认证
contentCompressionEnabledbooleancontentCompressionEnabled----是否启用内容压缩
normalizeUribooleannormalizeUri----是否标准化 URI
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/httpclient/connectionmanager.html b/manual/2.3/httpclient/connectionmanager.html new file mode 100644 index 0000000..795e35b --- /dev/null +++ b/manual/2.3/httpclient/connectionmanager.html @@ -0,0 +1,23 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

连接管理器

+

连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponentsokhttp3 的文档。

+

您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponentsokhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。

+

关于 okhttp 连接管理器

+

okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/httpclient/index.html b/manual/2.3/httpclient/index.html new file mode 100644 index 0000000..5b2ea20 --- /dev/null +++ b/manual/2.3/httpclient/index.html @@ -0,0 +1,113 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

apache httpcomponentsokhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponentsokhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。

+

传统的方式:

+
<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import org.apache.http.HttpResponse;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.client.methods.HttpPost;
+
+HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();
+
+HttpResponse response = httpClient.execute(new HttpPost("https://www.buession.com/"));
+
+

或者

+
<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import okhttp3.HttpClientConnectionManager;
+import okhttp3.OkHttpClient;
+import okhttp3.ConnectionPool;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+
+OkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());
+HttpClient httpClient = builder.build();
+
+Builder requestBuilder = new Builder().post();
+requestBuilder.url("https://www.buession.com/");
+Request okHttpRequest = requestBuilder.build();
+
+Response httpResponse = httpClient.newCall(okHttpRequest).execute();
+
+

现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpcore</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>org.apache.httpcomponents</groupId>
+    <artifactId>httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

或者

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-httpclient</artifactId>
+    <version>x.x.x</version>
+</dependency>
+<dependency>
+    <groupId>com.squareup.okhttp3</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.OkHttpHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.conn.OkHttpClientConnectionManager;
+import com.buession.httpclient.core.Response;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/httpclient/method.html b/manual/2.3/httpclient/method.html new file mode 100644 index 0000000..96a457d --- /dev/null +++ b/manual/2.3/httpclient/method.html @@ -0,0 +1,158 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

方法

+

buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。

+

示例:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/");
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/");
+
+

您可以自定义请求头:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.List;
+import java.util.ArrayList;
+
+List<Header> headers = new ArrayList<>();
+
+headers.add(new Header("X-SDK-NAME", "Buession"));
+headers.add(new Header("X-Timestamp", System.currentTimeMillis()));
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", headers);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", headers);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", headers);
+
+

您可以设置请求参数:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import java.util.Map;
+import java.util.HashMap;
+
+Map<String, Object> parameters = new HashMap<>();
+
+parameters.put("action", "edit");
+parameters.put("id", 1);
+
+// GET 请求
+Response response = httpClient.get("https://www.buession.com/", parameters);
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", parameters);
+
+// HEAD 请求
+Response response = httpClient.head("https://www.buession.com/", parameters);
+
+

您可以设置请求体:

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.core.Response;
+import com.buession.httpclient.core.Header;
+import jcom.buession.httpclient.core.RequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+import jcom.buession.httpclient.core.EncodedFormRequestBody;
+
+EncodedFormRequestBody requestBody = new EncodedFormRequestBody();
+
+requestBody.addRequestBodyElement("username", "buession");
+requestBody.addRequestBodyElement("password", "buession");
+
+// POST 请求
+Response response = httpClient.post("https://www.buession.com/", requestBody);
+
+JsonRawRequestBody requestBody = new JsonRawRequestBody(new User());
+// PUT 请求
+Response response = httpClient.put("https://www.buession.com/", requestBody);
+
+

不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody

+

RequestBody

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RequestBodyContent-Type说明
InputStreamRequestBodyapplication/octet-stream二进制请求体
ChunkedInputStreamRequestBodyapplication/octet-streamChunked 二进制请求体
RepeatableInputStreamRequestBodyapplication/octet-streamRepeatable 二进制请求体
EncodedFormRequestBodyapplication/x-www-form-urlencoded普通表单请求体
MultipartFormRequestBodymultipart/form-data文件上传表单请求体
HtmlRawRequestBodytext/htmlHTML 请求体
JavaScriptRawRequestBodyapplication/javascriptJavaScript 请求体
JsonRawRequestBodyapplication/jsonJSON 请求体
TextRawRequestBodytext/plainTEXT 请求体
XmlRawRequestBodytext/xmlXML 请求体
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/httpclient/response.html b/manual/2.3/httpclient/response.html new file mode 100644 index 0000000..c235301 --- /dev/null +++ b/manual/2.3/httpclient/response.html @@ -0,0 +1,37 @@ +buession-httpclient 参考手册-参考手册

buession-httpclient 参考手册

+

响应

+

当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。 +buession-httpclient 会将 apache httpcomponentsokhttp3 的响应对象,转换为 Response

+

需要注意的是,原生 apache httpcomponentsokhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。

+
import com.buession.httpclient.HttpClient;
+import com.buession.httpclient.ApacheHttpClient;
+import com.buession.httpclient.conn.ApacheClientConnectionManager;
+import com.buession.httpclient.core.Response;
+import java.io.InputStream;
+
+HttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());
+
+Response response = httpClient.post("https://www.buession.com/");
+InputStream stream = response.getInputStream(); // 以流的形式获取响应体
+String body = response.getBody(); // 以字符串的形式获取响应体
+
+stream.close();
+
+

getInputStreamgetBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponentsokhttp3 返回的流。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/index.html b/manual/2.3/index.html new file mode 100644 index 0000000..97c5ecd --- /dev/null +++ b/manual/2.3/index.html @@ -0,0 +1,114 @@ +API 参考手册-参考手册

API 参考手册

+

Buession Framework API 包含以下目录:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
模块使用帮助手册
buession-aop使用帮助API 手册
buession-beans使用帮助API 手册
buession-core使用帮助API 手册
buession-cron使用帮助API 手册
buession-dao使用帮助API 手册
buession-geoip使用帮助API 手册
buession-httpclient使用帮助API 手册
buession-io使用帮助API 手册
buession-jdbc使用帮助API 手册
buession-json使用帮助API 手册
buession-lang使用帮助API 手册
buession-net使用帮助API 手册
buession-redis使用帮助API 手册
buession-session使用帮助API 手册
buession-thesaurus使用帮助API 手册
buession-velocity使用帮助API 手册
buession-web使用帮助API 手册
+
\ No newline at end of file diff --git a/manual/2.3/io/index.html b/manual/2.3/io/index.html new file mode 100644 index 0000000..af19cbc --- /dev/null +++ b/manual/2.3/io/index.html @@ -0,0 +1,86 @@ +buession-io 参考手册-参考手册

buession-io 参考手册

+

封装了对文件的操作

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-io</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块二次封装了 java java.io.Filejava.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。

+

读取文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+byte[] result = file.read();
+
+

写文件

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+file.write("Buession");
+file.write("Buession".getBytes());
+file.write("Buession", true); // 追加写
+
+

获取文件 MD5、SHA-1值

+
import com.buession.io.file.File;
+
+File file = new File("/tmp/debug.txt");
+
+String md5 = file.getMd5(); // 获取文件 MD5
+String sha1 = file.getSha1(); // 获取文件 SHA-1
+
+

获取文件 MD5、SHA-1 值

+
import com.buession.io.file.File;
+import com.buession.io.MimeType;
+
+File file = new File("/tmp/debug.txt");
+
+MimeType result = file.getMimeType();
+
+

设置文件权限

+
import com.buession.io.file.Files;
+
+Files.chmod("/tmp/debug.txt", 0777);
+
+

设置文件用户组

+
import com.buession.io.file.Files;
+
+Files.chgrp("/tmp/debug.txt", "root");
+
+

设置文件用户

+
import com.buession.io.file.Files;
+
+Files.chown("/tmp/debug.txt", "root");
+
+

注解

+

注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。

+
import com.buession.io.json.annotation.MimeTypeString;
+
+class File {
+
+    @MimeTypeString
+    private MimeType mime;
+
+}
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/jdbc/index.html b/manual/2.3/jdbc/index.html new file mode 100644 index 0000000..2030bed --- /dev/null +++ b/manual/2.3/jdbc/index.html @@ -0,0 +1,28 @@ +buession-jdbc 参考手册-参考手册

buession-jdbc 参考手册

+

JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-jdbc</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

通过提供的 API,您可以简化对 DBCP2DruidHikariTomcat 数据源的初始化,该类库基本不单独使用。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/json/index.html b/manual/2.3/json/index.html new file mode 100644 index 0000000..95bf39b --- /dev/null +++ b/manual/2.3/json/index.html @@ -0,0 +1,63 @@ +buession-json 参考手册-参考手册

buession-json 参考手册

+

主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-json</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

封装了大量基于 jackson 的注解。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解说明
CalendarUnixTimestampjava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar
DateUnixTimestampjava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date
SqlDateUnixTimestampjava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date
TimestampUnixTimestampjava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp
JsonEnum2Map枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举
Sensitive通过该注解可以实现数据的脱敏
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/lang/index.html b/manual/2.3/lang/index.html new file mode 100644 index 0000000..cb435d0 --- /dev/null +++ b/manual/2.3/lang/index.html @@ -0,0 +1,27 @@ +buession-lang 参考手册-参考手册

buession-lang 参考手册

+

常用 POJO 类和枚举的定义,详细查看 API 参考手册。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-lang</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/net/index.html b/manual/2.3/net/index.html new file mode 100644 index 0000000..6ed50ce --- /dev/null +++ b/manual/2.3/net/index.html @@ -0,0 +1,35 @@ +buession-net 参考手册-参考手册

buession-net 参考手册

+

网络相关工具类。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-net</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

IP 地址工具类

+

IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。

+
import com.buession.net.utils.InetAddressUtis;
+
+long result = InetAddressUtis.ip2long("127.0.0.1"); // 2130706433
+String ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1
+
+

URI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/redis/datasource.html b/manual/2.3/redis/datasource.html new file mode 100644 index 0000000..ecac1c2 --- /dev/null +++ b/manual/2.3/redis/datasource.html @@ -0,0 +1,185 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

数据源

+

buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。 +通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。

+

数据源 DataSource 包括三个子接口:

+
    +
  • StandaloneDataSource:单机模式数据源
  • +
  • SentinelDataSource:哨兵模式数据源
  • +
  • ClusterDataSource:集群模式数据源
  • +
+

jedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。

+

在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。

+
<bean id="redisDataSource" class="com.buession.redis.client.connection.datasource.jedis.UserMapper"
+	p:host="${redis.host}"
+	p:port="${redis.port}"
+	p:password="${redis.password}" />
+
+

测试环境 properties:

+
redis.host=127.0.0.1
+redis.port=6379
+redis.password=
+
+

生产环境 properties:

+
redis.host=192.168.100.131
+redis.port=6379
+redis.password=passwd
+
+

连接池

+

通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfigConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。

+

连接池配置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置项数据类型-- 默认值说明
lifobooleanGenericObjectPoolConfig.DEFAULT_LIFO池模式,为 true 时,后进先出;为 false 时,先进先出
fairnessbooleanGenericObjectPoolConfig.DEFAULT_FAIRNESS当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制
maxWaitDurationGenericObjectPoolConfig.DEFAULT_MAX_WAIT当连接池资源用尽后,调用者获取连接时的最大等待时间
minEvictableIdleTimeDuration60000连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除
softMinEvictableIdleTimeDurationGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数
evictionPolicyClassNameStringGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME驱逐策略的类名
evictorShutdownTimeoutDurationGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT关闭驱逐线程的超时时间
numTestsPerEvictionRunint-1检测空闲对象线程每次运行时检测的空闲对象的数量
testOnCreatebooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE在创建对象时检测对象是否有效,配置 true 会降低性能
testOnBorrowbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW在从对象池获取对象时是否检测对象有效,配置 true 会降低性能
testOnReturnbooleanGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能
testWhileIdlebooleantrue在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
blockWhenExhaustedbooleanGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)
timeBetweenEvictionRunsint30000空闲连接检测的周期,如果为负值,表示不运行检测线程
jmxEnabledbooleanGenericObjectPoolConfig.DEFAULT_JMX_ENABLE是否注册 JMX
jmxNamePrefixStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIXJMX 前缀
jmxNameBaseStringGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE使用 base + jmxNamePrefix + i 来生成 ObjectName
maxTotalintGenericObjectPoolConfig.DEFAULT_MAX_TOTAL最大连接数
minIdleintGenericObjectPoolConfig.DEFAULT_MIN_IDLE最小空闲连接数
maxIdleintGenericObjectPoolConfig.DEFAULT_MAX_IDLE最大空闲连接数
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/redis/index.html b/manual/2.3/redis/index.html new file mode 100644 index 0000000..8179426 --- /dev/null +++ b/manual/2.3/redis/index.html @@ -0,0 +1,51 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-redis</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

介绍

+

buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。

+
import com.buession.redis.RedisTemplate;
+import com.buession.redis.core.Options;
+import com.buession.core.serializer.type.TypeReference;
+import java.utils.Map;
+import java.utils.HashMap;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.setOptions(new Options());
+redisTemplate.afterPropertiesSet();
+
+// 将 User 对象写进 key 为 user hash 中
+redisTemplate.hSet("user", "1", new User());
+
+// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User
+User user = redisTemplate.hGetObject("user", "1", User.class);
+
+// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User
+Map<String, User> data = redisTemplate.hGetAllObject("user", "1", new TypeReference<HashMap<String, User>>{});
+
+

展望

+

目前,buession-redis 仅支持 jedis,不支持 lettuce,我们计划在 2.3 ~ 2.5 的版本中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/redis/method.html b/manual/2.3/redis/method.html new file mode 100644 index 0000000..e928159 --- /dev/null +++ b/manual/2.3/redis/method.html @@ -0,0 +1,49 @@ +buession-redis 参考手册-参考手册

buession-redis 参考手册

+

方法

+

buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。

+
import com.buession.redis.BaseRedisTemplate;
+
+BaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 删除哈希表 key 中的一个或多个指定域
+redisTemplate.hDel("user", "1", "2", "3");
+
+// 检查给定 key 是否存在
+redisTemplate.exists("user");
+
+// 获取列表 key 中,下标为 index 的元素
+redisTemplate.lIndex("user", 1);
+
+// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾
+redisTemplate.append("key", "value 1");
+
+

BaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。

+
import com.buession.redis.RedisTemplate;
+
+RedisTemplate redisTemplate = new RedisTemplate(dataSource);
+
+redisTemplate.afterPropertiesSet();
+
+// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类
+User user = redisTemplate.lIndexObject("user", 1, User.class);
+
+

序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/session/index.html b/manual/2.3/session/index.html new file mode 100644 index 0000000..3181fad --- /dev/null +++ b/manual/2.3/session/index.html @@ -0,0 +1,28 @@ +buession-session 参考手册-参考手册

buession-session 参考手册

+

无文档

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-session</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该模块无实际意义,将在今后的版本中会删除掉。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/thesaurus/index.html b/manual/2.3/thesaurus/index.html new file mode 100644 index 0000000..3834679 --- /dev/null +++ b/manual/2.3/thesaurus/index.html @@ -0,0 +1,37 @@ +buession-thesaurus 参考手册-参考手册

buession-thesaurus 参考手册

+

对词库的解析,目前仅支持搜狗词条。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-thesaurus</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。

+
import com.buession.thesaurus.SogouParser;
+import com.buession.thesaurus.Parser;
+import com.buession.thesaurus.core.Word;
+import java.util.Set;
+
+Parser parser = new SogouParser();
+
+Set<Word> words parser.parse("搜谱拼音词条文件路径");
+
+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/velocity/index.html b/manual/2.3/velocity/index.html new file mode 100644 index 0000000..08b65a1 --- /dev/null +++ b/manual/2.3/velocity/index.html @@ -0,0 +1,28 @@ +buession-velocity 参考手册-参考手册

buession-velocity 参考手册

+

spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-velocity</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/web/annotation.html b/manual/2.3/web/annotation.html new file mode 100644 index 0000000..b9a55ad --- /dev/null +++ b/manual/2.3/web/annotation.html @@ -0,0 +1,152 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

注解

+

我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。

+

注解

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注解Request / Response作用域说明
@RequestClientIprequest方法参数获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress
@ContentTyperesponse类、方法设置响应 Content-Type
@HttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值
@DisableHttpCacheresponse类、方法设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存
@ResponseHeaderresponse类、方法设置响应头
@ResponseHeadersresponse类、方法批量设置响应头
@DocumentMetaDataresponse类、方法设置页面标题、页面编码、关键字、描述、版权等等元信息
+

获取用户端真实 IP

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/ip1")
+	@ResponseBody
+	public String ip1(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip2")
+	@ResponseBody
+	public Long ip2(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) long ip, ServerHttpResponse response){
+		return ip;
+	}
+
+	@RequestMapping(path = "/ip3")
+	@ResponseBody
+	public InetAddress ip3(@RequestClientIp(headerName = {"X-Real-Ip", "X-User-Real-Ip"}) InetAddress ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

设置页面缓存

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+

以上,会自动计算 Cache-Controlpragma 的值。当然,您也可以手动指定。

+
@Controller
+@RequestMapping(path = "/test")
+public class TestController {
+
+	@RequestMapping(path = "/cache")
+	@HttpCache(expires = "5", cacheControl="public, max-age=5")
+	@ResponseBody
+	public String cache(@RequestClientIp String ip, ServerHttpResponse response){
+		return ip;
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.3/web/filter.html b/manual/2.3/web/filter.html new file mode 100644 index 0000000..3671532 --- /dev/null +++ b/manual/2.3/web/filter.html @@ -0,0 +1,51 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

过滤器

+

我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。

+

servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。

+

过滤器

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
过滤器说明
PoweredByFilterPowered By 过滤器
PrintUrlFilter打印当前请求 URL 过滤器
ResponseHeaderFilter响应头过滤器,设置响应头
ResponseHeadersFilter响应头过滤器,批量设置响应头
ServerInfoFilterServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点
+
\ No newline at end of file diff --git a/manual/2.3/web/index.html b/manual/2.3/web/index.html new file mode 100644 index 0000000..2882f04 --- /dev/null +++ b/manual/2.3/web/index.html @@ -0,0 +1,28 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。

+
+

安装

+
<dependency>
+    <groupId>com.buession</groupId>
+    <artifactId>buession-web</artifactId>
+    <version>x.x.x</version>
+</dependency>
+
+

buession-web 扩展了 spring-webmvcspring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。

+

API 参考手册>>

+
\ No newline at end of file diff --git a/manual/2.3/web/pagination.html b/manual/2.3/web/pagination.html new file mode 100644 index 0000000..dadbe5d --- /dev/null +++ b/manual/2.3/web/pagination.html @@ -0,0 +1,177 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

Response

+

我们定义了 restful 返回数据类型中的分页对象。

+
public class Pagination {
+
+	/**
+	 * 默认每页大小
+	 */
+	public final static int PAGESIZE = 15;
+
+	/**
+	 * 当前页码
+	 */
+	private int page = 1;
+
+	/**
+	 * 每页大小
+	 */
+	private int pagesize = PAGESIZE;
+
+	/**
+	 * 前一页页码
+	 */
+	private int previousPage = 1;
+
+	/**
+	 * 下一页页码
+	 */
+	private int nextPage = 1;
+
+	/**
+	 * 总页码
+	 */
+	private int totalPages = 1;
+
+	/**
+	 * 总记录数
+	 */
+	private long totalRecords = 0;
+
+	/**
+	 * Constructs with default configuration.
+	 */
+	public Pagination();
+
+	/**
+	 * Constructs with page and pagesize.
+	 *
+	 * @param page
+	 * 		当前页码
+	 * @param pagesize
+	 * 		每页大小
+	 */
+	public Pagination(int page, int pagesize);
+
+	/**
+	 * Constructs with page, pagesize and totalRecords.
+	 *
+	 * @param page
+	 * 		当前页码
+	 * @param pagesize
+	 * 		每页大小
+	 * @param totalRecords
+	 * 		总记录数
+	 */
+	public Pagination(int page, int pagesize, long totalRecords);
+
+	/**
+	 * 返回当前页码
+	 *
+	 * @return 当前页码
+	 */
+	public int getPage();
+
+	/**
+	 * 设置当前页码
+	 *
+	 * @param page
+	 * 		当前页码
+	 */
+	public void setPage(int page);
+
+	/**
+	 * 返回每页大小
+	 *
+	 * @return 每页大小
+	 */
+	public int getPagesize();
+
+	/**
+	 * 设置每页大小
+	 *
+	 * @param pagesize
+	 * 		每页大小
+	 */
+	public void setPagesize(int pagesize);
+
+	/**
+	 * 返回前一页页码
+	 *
+	 * @return 前一页页码
+	 */
+	public int getPreviousPage();
+
+	/**
+	 * 设置前一页页码
+	 *
+	 * @param previousPage
+	 * 		前一页页码
+	 */
+	public void setPreviousPage(int previousPage);
+
+	/**
+	 * 返回下一页页码
+	 *
+	 * @return 下一页页码
+	 */
+	public int getNextPage(){
+		return nextPage;
+	}
+
+	/**
+	 * 设置下一页页码
+	 *
+	 * @param nextPage
+	 * 		下一页页码
+	 */
+	public void setNextPage(int nextPage);
+
+	/**
+	 * 返回总页码
+	 *
+	 * @return 总页码
+	 */
+	public int getTotalPages();
+
+	/**
+	 * 设置总页码
+	 *
+	 * @param totalPages
+	 * 		总页码
+	 */
+	public void setTotalPages(int totalPages);
+
+	/**
+	 * 返回总记录数
+	 *
+	 * @return 总记录数
+	 */
+	public long getTotalRecords();
+
+	/**
+	 * 设置总记录数
+	 *
+	 * @param totalRecords
+	 * 		总记录数
+	 */
+	public void setTotalRecords(long totalRecords);
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.3/web/response.html b/manual/2.3/web/response.html new file mode 100644 index 0000000..66c692b --- /dev/null +++ b/manual/2.3/web/response.html @@ -0,0 +1,263 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

Response

+

我们定义了 restful 返回数据类型。

+
public class Response<E> {
+
+	/**
+	 * 状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 */
+	private boolean state;
+
+	/**
+	 * 错误状态码
+	 */
+	private int code;
+
+	/**
+	 * 提示或错误消息
+	 */
+	private String message;
+
+	/**
+	 * 数据
+	 */
+	private E data;
+
+	/**
+	 * 分页对象
+	 */
+	private Pagination pagination;
+
+	/**
+	 * 构造函数
+	 */
+	public Response();
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 */
+	public Response(boolean state);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param code
+	 * 		错误码
+	 */
+	public Response(boolean state, int code);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param code
+	 * 		错误码
+	 * @param message
+	 * 		提示或错误消息
+	 */
+	public Response(boolean state, int code, String message);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param message
+	 * 		提示或错误消息
+	 */
+	public Response(boolean state, String message);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param code
+	 * 		错误码
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 */
+	public Response(boolean state, int code, String message, E data);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 */
+	public Response(boolean state, String message, E data);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param code
+	 * 		错误码
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 * @param pagination
+	 * 		分页对象
+	 */
+	public Response(boolean state, int code, String message, E data, Pagination pagination);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param code
+	 * 		错误码
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 * @param pagination
+	 * 		分页对象
+	 */
+	@Deprecated
+	public Response(boolean state, int code, String message, E data, com.buession.core.Pagination<E> pagination) ;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 * @param pagination
+	 * 		分页对象
+	 */
+	public Response(boolean state, String message, E data, Pagination pagination);
+
+	/**
+	 * 构造函数
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 * @param message
+	 * 		提示或错误消息
+	 * @param data
+	 * 		数据
+	 * @param pagination
+	 * 		分页对象
+	 */
+	@Deprecated
+	public Response(boolean state, String message, E data, com.buession.core.Pagination<E> pagination);
+
+	/**
+	 * 返回状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 *
+	 * @return 状态
+	 */
+	public boolean isState();
+
+	/**
+	 * 返回状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 *
+	 * @return 状态
+	 */
+	public boolean getState();
+
+	/**
+	 * 设置状态
+	 *
+	 * @param state
+	 * 		状态,true 为逻辑成功;false 为逻辑失败或出现异常
+	 */
+	public void setState(boolean state);
+
+	/**
+	 * 返回错误码
+	 *
+	 * @return 错误码
+	 */
+	public int getCode();
+
+	/**
+	 * 设置错误码
+	 *
+	 * @param code
+	 * 		错误码
+	 */
+	public void setCode(int code);
+
+	/**
+	 * 返回提示或错误消息
+	 *
+	 * @return 提示或错误消息
+	 */
+	public String getMessage();
+
+	/**
+	 * 设置提示或错误消息
+	 *
+	 * @param message
+	 * 		提示或错误消息
+	 */
+	public void setMessage(String message);
+
+	/**
+	 * 返回数据
+	 *
+	 * @return 数据
+	 */
+	public E getData();
+
+	/**
+	 * 设置数据
+	 *
+	 * @param data
+	 * 		数据
+	 */
+	public void setData(E data);
+
+	/**
+	 * 返回分页对象
+	 *
+	 * @return 分页对象
+	 */
+	public Pagination getPagination();
+
+	/**
+	 * 设置分页对象
+	 *
+	 * @param pagination
+	 * 		分页对象
+	 */
+	public void setPagination(Pagination pagination);
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.3/web/restful.html b/manual/2.3/web/restful.html new file mode 100644 index 0000000..f0fe99b --- /dev/null +++ b/manual/2.3/web/restful.html @@ -0,0 +1,47 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

RESTFUL

+

Restful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。

+

我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。

+
@RestController
+@RequestMapping(path = "/example")
+public class ExampleController extends AbstractRestController<Integer, ExampleDto, ExampleVo> {
+
+	@Override
+	public Response<ExampleVo> add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){
+		
+	}
+
+	@Override
+	public Response<ExampleVo> edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id, @RequestBody ExampleDto example){
+
+	}
+
+	@Override
+	public Response<ExampleVo> detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+
+	}
+
+	@Override
+	public Response<ExampleVo> delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = "id") Integer id){
+		
+	}
+
+}
+
+
\ No newline at end of file diff --git a/manual/2.3/web/utils.html b/manual/2.3/web/utils.html new file mode 100644 index 0000000..74daba4 --- /dev/null +++ b/manual/2.3/web/utils.html @@ -0,0 +1,35 @@ +buession-web 参考手册-参考手册

buession-web 参考手册

+

工具

+

我们封装了一些 web 相关的工具类,用于处理 request、response。

+

servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。

+

获取客户端真实 IP 地址:

+
RequestUtils.getClientIp(request);
+
+

我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。

+

优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr

+

是否是 Ajax 请求:

+
RequestUtils.isAjaxRequest(request);
+
+

是否是移动设备请求:

+
RequestUtils.isMobile(request);
+
+

设置缓存:

+
ResponseUtils.httpCache(response, 5); // 缓存 5 秒
+ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点
+
+
\ No newline at end of file diff --git a/manual/index.html b/manual/index.html new file mode 100644 index 0000000..73b4968 --- /dev/null +++ b/manual/index.html @@ -0,0 +1,19 @@ +参考手册简介-参考手册

参考手册简介

+

Buession Framework 它是日常工作中常见的通用技术需求二次封装,提供了众多常用的类库、方法、注解;同时基于 springfrawork、jsckson、jedis、apache httpcomponents、okhttp3 等等众多的优秀的三方工具的标准化的、统一的类库的上层封装,简化框架切换带来的成本。更多介绍开源查阅框架介绍

+

本章节将想您讲解,如何使用 Buession Framework,为您提供 Java 应用的最佳实践。

+
\ No newline at end of file diff --git a/manual/overview.html b/manual/overview.html new file mode 100644 index 0000000..338bc66 --- /dev/null +++ b/manual/overview.html @@ -0,0 +1,44 @@ +参考指南-参考手册

参考指南

+

本文档包含了完整的 Buession Framework 的参考文档。

+ + + + + + + + + + + + + + + + + + + + + + + + + +
版本手册
2.3.xAPI 手册
2.2.xAPI 手册
2.1.xAPI 手册
2.0.xAPI 手册
+
\ No newline at end of file diff --git a/search_json.js b/search_json.js new file mode 100644 index 0000000..50c76fb --- /dev/null +++ b/search_json.js @@ -0,0 +1,4294 @@ +window.ydoc_plugin_search_json = { + "文档": [ + { + "title": "快速入门", + "content": "TIP\n\n官方指南假设您已了解\"JAVA\"方面的相关知识。\n\nBuession Framework 它是日常工作中常见的通用技术需求二次封装,提供了众多常用的类库、方法、注解;同时基于 springfrawork、jsckson、jedis、apache httpcomponents、okhttp3 等等众多的优秀的三方工具的标准化的、统一的类库的上层封装,简化框架切换带来的成本。更多介绍开源查阅框架介绍。Buession Framework 还在引用三方类库时,确保了版本的一致性,避免在不用三方类库引用的同一个三方类库版本不一致的情况。您可以根据本文档中的示例,快速熟悉 Buession Framework 的使用方法。", + "url": "/docs/quickstart.html", + "children": [ + { + "title": "下一步可做什么?", + "url": "/docs/quickstart.html#下一步可做什么?", + "content": "下一步可做什么?您对 Buession Framework 大致了解后,您接下来可以做以下事情:了解兼容性:了解 Buession Framework 的兼容性\n安装:安装/引用 Buession Framework\n使用:开始使用 Buession Framework 功能\n" + } + ] + }, + { + "title": "框架介绍", + "content": "", + "url": "/docs/intro.html", + "children": [ + { + "title": "Buession Framework 框架是什么?", + "url": "/docs/intro.html#buession-framework-框架是什么?", + "content": "Buession Framework 框架是什么?Buession Framework 框架不是重复造车轮,它不是其它框架的替代品。它是基于各开源框架的日常工作中常见的通用技术需求二次封装本地化的数据验证,如:QQ、电话号码、身份证号码、邮政编码\n常用 DAO 层操作,如:插入、替换、根据主键获取记录、获取单条记录、获取多条记录\n应用层实现数据库读写分离\nredis 操作兼容原生 API 的前提下,同时实现了 redis 中的值反序列化成对象\n词库解析(目前仅支持搜狗词库)\n使用 WEB 功能,如:响应头注解、缓存头注解、兼容性获取用户端真实 IP、获取用户真实 IP 注解\n替代 springfamework 5,支持 apache velocity\n基于 maxmind geoip 的 IP 信息解析\n基于标准的 HTTP 请求方法的 HttpClient\n文件操作,如:写文件、设置文件所属用户或组、文件 MimeType 解析\n... ...它是同类开源框架的一种兼容性的上层封装,简化框架切换带来的成本摒弃直接使用原生类库,带来的大量的代码修改,如:HttpClient 支持 apache httpcomponents 和 okhttp3,只需要修改 HttpClient 初始化类,即可实现 HTTP 库的切换\n... ..." + } + ] + }, + { + "title": "开源协议", + "content": " Apache License Version 2.0, January 2004\n http://www.apache.org/licenses/\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\nDefinitions.\n\"License\" shall mean the terms and conditions for use, reproduction,\nand distribution as defined by Sections 1 through 9 of this document.\n\"Licensor\" shall mean the copyright owner or entity authorized by\nthe copyright owner that is granting the License.\n\"Legal Entity\" shall mean the union of the acting entity and all\nother entities that control, are controlled by, or are under common\ncontrol with that entity. For the purposes of this definition,\n\"control\" means (i) the power, direct or indirect, to cause the\ndirection or management of such entity, whether by contract or\notherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\"You\" (or \"Your\") shall mean an individual or Legal Entity\nexercising permissions granted by this License.\n\"Source\" form shall mean the preferred form for making modifications,\nincluding but not limited to software source code, documentation\nsource, and configuration files.\n\"Object\" form shall mean any form resulting from mechanical\ntransformation or translation of a Source form, including but\nnot limited to compiled object code, generated documentation,\nand conversions to other media types.\n\"Work\" shall mean the work of authorship, whether in Source or\nObject form, made available under the License, as indicated by a\ncopyright notice that is included in or attached to the work\n(an example is provided in the Appendix below).\n\"Derivative Works\" shall mean any work, whether in Source or Object\nform, that is based on (or derived from) the Work and for which the\neditorial revisions, annotations, elaborations, or other modifications\nrepresent, as a whole, an original work of authorship. For the purposes\nof this License, Derivative Works shall not include works that remain\nseparable from, or merely link (or bind by name) to the interfaces of,\nthe Work and Derivative Works thereof.\n\"Contribution\" shall mean any work of authorship, including\nthe original version of the Work and any modifications or additions\nto that Work or Derivative Works thereof, that is intentionally\nsubmitted to Licensor for inclusion in the Work by the copyright owner\nor by an individual or Legal Entity authorized to submit on behalf of\nthe copyright owner. For the purposes of this definition, \"submitted\"\nmeans any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems,\nand issue tracking systems that are managed by, or on behalf of, the\nLicensor for the purpose of discussing and improving the Work, but\nexcluding communication that is conspicuously marked or otherwise\ndesignated in writing by the copyright owner as \"Not a Contribution.\"\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\non behalf of whom a Contribution has been received by Licensor and\nsubsequently incorporated within the Work.\n\n\nGrant of Copyright License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\ncopyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the\nWork and such Derivative Works in Source or Object form.\n\n\nGrant of Patent License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n(except as stated in this section) patent license to make, have made,\nuse, offer to sell, sell, import, and otherwise transfer the Work,\nwhere such license applies only to those patent claims licensable\nby such Contributor that are necessarily infringed by their\nContribution(s) alone or by combination of their Contribution(s)\nwith the Work to which such Contribution(s) was submitted. If You\ninstitute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work\nor a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses\ngranted to You under this License for that Work shall terminate\nas of the date such litigation is filed.\n\n\nRedistribution. You may reproduce and distribute copies of the\nWork or Derivative Works thereof in any medium, with or without\nmodifications, and in Source or Object form, provided that You\nmeet the following conditions:\n(a) You must give any other recipients of the Work or\nDerivative Works a copy of this License; and\n(b) You must cause any modified files to carry prominent notices\nstating that You changed the files; and\n(c) You must retain, in the Source form of any Derivative Works\nthat You distribute, all copyright, patent, trademark, and\nattribution notices from the Source form of the Work,\nexcluding those notices that do not pertain to any part of\nthe Derivative Works; and\n(d) If the Work includes a \"NOTICE\" text file as part of its\ndistribution, then any Derivative Works that You distribute must\ninclude a readable copy of the attribution notices contained\nwithin such NOTICE file, excluding those notices that do not\npertain to any part of the Derivative Works, in at least one\nof the following places: within a NOTICE text file distributed\nas part of the Derivative Works; within the Source form or\ndocumentation, if provided along with the Derivative Works; or,\nwithin a display generated by the Derivative Works, if and\nwherever such third-party notices normally appear. The contents\nof the NOTICE file are for informational purposes only and\ndo not modify the License. You may add Your own attribution\nnotices within Derivative Works that You distribute, alongside\nor as an addendum to the NOTICE text from the Work, provided\nthat such additional attribution notices cannot be construed\nas modifying the License.\nYou may add Your own copyright statement to Your modifications and\nmay provide additional or different license terms and conditions\nfor use, reproduction, or distribution of Your modifications, or\nfor any such Derivative Works as a whole, provided Your use,\nreproduction, and distribution of the Work otherwise complies with\nthe conditions stated in this License.\n\n\nSubmission of Contributions. Unless You explicitly state otherwise,\nany Contribution intentionally submitted for inclusion in the Work\nby You to the Licensor shall be under the terms and conditions of\nthis License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify\nthe terms of any separate license agreement you may have executed\nwith Licensor regarding such Contributions.\n\n\nTrademarks. This License does not grant permission to use the trade\nnames, trademarks, service marks, or product names of the Licensor,\nexcept as required for reasonable and customary use in describing the\norigin of the Work and reproducing the content of the NOTICE file.\n\n\nDisclaimer of Warranty. Unless required by applicable law or\nagreed to in writing, Licensor provides the Work (and each\nContributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied, including, without limitation, any warranties or conditions\nof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\nPARTICULAR PURPOSE. You are solely responsible for determining the\nappropriateness of using or redistributing the Work and assume any\nrisks associated with Your exercise of permissions under this License.\n\n\nLimitation of Liability. In no event and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise,\nunless required by applicable law (such as deliberate and grossly\nnegligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special,\nincidental, or consequential damages of any character arising as a\nresult of this License or out of the use or inability to use the\nWork (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses), even if such Contributor\nhas been advised of the possibility of such damages.\n\n\nAccepting Warranty or Additional Liability. While redistributing\nthe Work or Derivative Works thereof, You may choose to offer,\nand charge a fee for, acceptance of support, warranty, indemnity,\nor other liability obligations and/or rights consistent with this\nLicense. However, in accepting such obligations, You may act only\non Your own behalf and on Your sole responsibility, not on behalf\nof any other Contributor, and only if You agree to indemnify,\ndefend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason\nof your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONSAPPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\nCopyright [yyyy] [name of copyright owner]Licensed under the Apache License, Version 2.0 (the \"License\");you may not use this file except in compliance with the License.\nYou may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.", + "url": "/docs/license.html", + "children": [] + }, + { + "title": "模块说明", + "content": "", + "url": "/docs/module.html", + "children": [ + { + "title": "buession-aop", + "url": "/docs/module.html#buession-aop", + "content": "buession-aopAOP 封装,方便实现自定义注解\n" + }, + { + "title": "buession-beans", + "url": "/docs/module.html#buession-beans", + "content": "buession-beansBean 工具类封\n" + }, + { + "title": "buession-core", + "url": "/docs/module.html#buession-core", + "content": "buession-core一些继承 apache lang3、apache collections4 的对字符串、集合、List、Map、Class、Object、Enum、Number等工具类封装和扩展\n汉字拼音工具类\n版本对比工具类\n数学计算\nIP 地址工具类\n进制转换\n对象序列化和反序列化,支持二进制、FastJson、Gson、Jackson\n数据合法性验证类\n带 code 和 message 消息消息对象,通过 properties 的形式注入\n日期对象类\nID 生成器\nManager 层注解\n" + }, + { + "title": "buession-cron", + "url": "/docs/module.html#buession-cron", + "content": "buession-cron对 quartz 的二次封装\n" + }, + { + "title": "buession-dao", + "url": "/docs/module.html#buession-dao", + "content": "buession-dao对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装\n从代码层面上支持数据库一主多从实现读写分离,insert、update、delete 操作主库,select 操作从库\n" + }, + { + "title": "buession-geoip", + "url": "/docs/module.html#buession-geoip", + "content": "buession-geoip对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息\n" + }, + { + "title": "buession-httpclient", + "url": "/docs/module.html#buession-httpclient", + "content": "buession-httpclient对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节\n屏蔽了对 post form、post json 等等的技术细节\n" + }, + { + "title": "buession-io", + "url": "/docs/module.html#buession-io", + "content": "buession-io封装了对文件的操作\n" + }, + { + "title": "buession-jdbc", + "url": "/docs/module.html#buession-jdbc", + "content": "buession-jdbcJDBC 通用 POJO 类定义\n对 Hikari、Dbcp2、Druid 等配置和数据源的封装\n" + }, + { + "title": "buession-json", + "url": "/docs/module.html#buession-json", + "content": "buession-json主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现\n" + }, + { + "title": "buession-lang", + "url": "/docs/module.html#buession-lang", + "content": "buession-lang常用枚举(如:状态-Status、性别-Gender 等)的定义\n常用 POJO 类(如:地理位置-Geo、Key Value-KeyValue 等)的定义\n" + }, + { + "title": "buession-net", + "url": "/docs/module.html#buession-net", + "content": "buession-net网络相关工具类\n" + }, + { + "title": "buession-redis", + "url": "/docs/module.html#buession-redis", + "content": "buession-redisRedis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 命令保持一致\n对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象\n" + }, + { + "title": "buession-session", + "url": "/docs/module.html#buession-session", + "content": "buession-session...\n" + }, + { + "title": "buession-thesaurus", + "url": "/docs/module.html#buession-thesaurus", + "content": "buession-thesaurus对词库的解析,目前仅支持搜狗词条\n" + }, + { + "title": "buession-velocity", + "url": "/docs/module.html#buession-velocity", + "content": "buession-velocityspring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity\n" + }, + { + "title": "buession-web", + "url": "/docs/module.html#buession-web", + "content": "buession-webweb 相关的功能封装,支持 servlet 和 reactive\n封装了一些常用注解,简化了业务层方面的代码实现\n封装了一些常用 filter\n" + } + ] + }, + { + "title": "版本说明", + "content": "该项目基于 GNU 版风格定义项目版本,即:主版本号.子版本号.修正版本号。管理策略主版本号,发生变更时,不保证所有的 API 对上一个版本兼容,但保障大部分能兼容;主版本变更,可能涉及类、接口、枚举、方法的删除,或者包名的变更\n子版本号,发生变更时,完全兼容上一个版本,主要会增加一些小的功能或API,底层逻辑的调整调优\n修正版本号,主要用于修复 BUG、优化性能、安全漏洞修复,不会新增、变更、删除已有 API\n三方包兼容性说明当引用的三方包,我们保证尽大可能兼容。但对于 springframework、springboot、springcloud、springsecurity、springdata 等 spring 家族组件,以及 servlet 兼容对应的主版本。", + "url": "/docs/version.html", + "children": [] + }, + { + "title": "安装及使用", + "content": "", + "url": "/docs/installation.html", + "children": [ + { + "title": "Maven 中央仓库搜索", + "url": "/docs/installation.html#maven-中央仓库搜索", + "content": "Maven 中央仓库搜索https://mvnrepository.com/search?q=com.buession\nhttps://search.maven.org/search?q=g:com.buession\n" + }, + { + "title": "手动编译", + "url": "/docs/installation.html#手动编译", + "content": "手动编译git clone https://github.com/buession/buessionframeworkcd buessionframework/buession-parent && mvn clean install\n" + }, + { + "title": "Maven", + "url": "/docs/installation.html#maven", + "content": "Maven com.buession\n buession-xxx\n x.x.x\n\n" + }, + { + "title": "Gradle", + "url": "/docs/installation.html#gradle", + "content": "Gradlecompile group: 'com.buession', name: 'buession-xxx', version: 'x.x.x'其中,artifactId 中的 xxx 表示对应的子模块;version 中的 x.x.x 代表版本号,根据需要使用特定版本,建议使用 maven 仓库中已构建好的最新版本的包。" + } + ] + }, + { + "title": "环境要求", + "content": "JDKJDK 8+构建工具\n\n构建工具\n版本\n\n\n\n\nMaven\n3.5+\n\n\nGradle\n6.x+,推荐 6.3 及以上版本\n\n\nServlet 容器支持 servlet 3.1+,推荐使用 servlet 4.0 及以上版本。", + "url": "/docs/requirement.html", + "children": [] + }, + { + "title": "下一步计划", + "content": "下一步计划,在 2.0.x 版本中,完善代码中的注释,便于生成的 javadoc 利于查阅,同步完善官网文档\n同时,对现有代码中存在的 BUG 进行修复,和代码层面的优化\n三方类库的小版本升级,在保证兼容性的前提下,仅大可能的通过升级三方依赖包的版本来修复其已知安全漏洞\n在 2.0.x 不会增加新的功能特性\n整个,大致时间计划,在2022国庆前后,终止 2.0.x 版本的开发。", + "url": "/docs/plan.html", + "children": [] + } + ], + "参考手册": [ + { + "title": "参考手册简介", + "content": "Buession Framework 它是日常工作中常见的通用技术需求二次封装,提供了众多常用的类库、方法、注解;同时基于 springfrawork、jsckson、jedis、apache httpcomponents、okhttp3 等等众多的优秀的三方工具的标准化的、统一的类库的上层封装,简化框架切换带来的成本。更多介绍开源查阅框架介绍。本章节将想您讲解,如何使用 Buession Framework,为您提供 Java 应用的最佳实践。", + "url": "/manual/index.html", + "children": [] + }, + { + "title": "参考指南", + "content": "本文档包含了完整的 Buession Framework 的参考文档。\n\n版本\n手册\n\n\n\n\n2.3.x\nAPI 手册\n\n\n2.2.x\nAPI 手册\n\n\n2.1.x\nAPI 手册\n\n\n2.0.x\nAPI 手册\n\n\n", + "url": "/manual/overview.html", + "children": [] + }, + { + "title": "API 参考手册", + "content": "Buession Framework API 包含以下目录:\n\n模块\n使用帮助\n手册\n\n\n\n\nbuession-aop\n使用帮助\nAPI 手册\n\n\nbuession-beans\n使用帮助\nAPI 手册\n\n\nbuession-core\n使用帮助\nAPI 手册\n\n\nbuession-cron\n使用帮助\nAPI 手册\n\n\nbuession-dao\n使用帮助\nAPI 手册\n\n\nbuession-geoip\n使用帮助\nAPI 手册\n\n\nbuession-httpclient\n使用帮助\nAPI 手册\n\n\nbuession-io\n使用帮助\nAPI 手册\n\n\nbuession-jdbc\n使用帮助\nAPI 手册\n\n\nbuession-json\n使用帮助\nAPI 手册\n\n\nbuession-lang\n使用帮助\nAPI 手册\n\n\nbuession-net\n使用帮助\nAPI 手册\n\n\nbuession-redis\n使用帮助\nAPI 手册\n\n\nbuession-session\n使用帮助\nAPI 手册\n\n\nbuession-thesaurus\n使用帮助\nAPI 手册\n\n\nbuession-velocity\n使用帮助\nAPI 手册\n\n\nbuession-web\n使用帮助\nAPI 手册\n\n\n", + "url": "/manual/2.3/index.html", + "children": [] + }, + { + "title": "buession-aop 参考手册", + "content": "AOP 封装,方便实现自定义注解", + "url": "/manual/2.3/aop/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/aop/index.html#安装", + "content": "安装 com.buession\n buession-aop\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/aop/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-beans 参考手册", + "content": "该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。", + "url": "/manual/2.3/beans/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/beans/index.html#安装", + "content": "安装 com.buession\n buession-beans\n x.x.x\n\n" + }, + { + "title": "属性拷贝", + "url": "/manual/2.3/beans/index.html#属性拷贝", + "content": "属性拷贝使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。import com.buession.beans.BeanUtils;\nBeanUtils.copyProperties(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.copyProperties(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "属性映射", + "url": "/manual/2.3/beans/index.html#属性映射", + "content": "属性映射使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。import com.buession.beans.BeanUtils;\nBeanUtils.populate(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.populate(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "Bean 转换为 Map", + "url": "/manual/2.3/beans/index.html#bean-转换为-map", + "content": "Bean 转换为 Map使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Keyimport com.buession.beans.BeanUtils;\nMap result = BeanUtils.toMap(bean)\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/beans/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。", + "url": "/manual/2.3/core/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/core/index.html#安装", + "content": "安装 com.buession\n buession-core\n x.x.x\n\n构建器Map、集合的便捷式构建,减少您的代码行数编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中收集器数组、Map、集合的工具类上下文定义应用上下文的类库、注解配置器接使用配置参数对对象进行配置定制器使用源对象对目标对象进行定制转换器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。日期时间日期、时间工具ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。数学函数定义了实用的数学函数序列化将对象序列化为二进制或 JSON。反序列化将二进制或 JSON 反序列化为对象。验证器数据验证器及其注解工具类常用通用性工具类其它通用的接口定义,框架自身类异常通用异常的定义" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/builder.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.3/core/builder.html#构建器", + "content": "构建器Map、集合的便捷式构建,减少您的代码行数。您需要往 Map、List 中添加元素的传统写法是:import java.util.ArrayList;import java.util.List;\nimport java.util.HashMap;\nimport java.util.Map;\n\nList list = new ArrayList();\nlist.add(\"A\");\nlist.add(\"B\");\nlist.add(\"C\");\n\nMap map = new HashMap();\nmap.put(\"a\", \"A\");\nmap.put(\"b\", \"B\");\nmap.put(\"c\", \"C\");\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.Map;\n\nList list = ListBuilder.create().add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create().put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.LinkedHashMap;\n\nList list = ListBuilder.create(LinkedList.class).add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create(LinkedHashMap.class).put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数\n当您有 value 为 null 时,不添加到 List 时,传统写法:import java.util.ArrayList;import java.util.List;\n\nString value = null;\nList list = new ArrayList();\n\nif(value != null){\n\tlist.add(value);\n}\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import java.util.List;\n\nString value = null;\nList list = ListBuilder.create().addIfPresent(value).build();\nMap、Set、Queue 同理。" + }, + { + "title": "便捷方法", + "url": "/manual/2.3/core/builder.html#构建器-便捷方法", + "content": "便捷方法\n\n方法\n说明\n\n\n\n\n List ListBuilder.epmty()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of(V value)\n创建仅有一个元素的 V 类型的 List 对象\n\n\n Queue QueueBuilder.epmty()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Queue 对象\n\n\n Set SetBuilder.epmty()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Set 对象\n\n\n Map MapBuilder.epmty()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of(V value)\n创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\nempty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/builder.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/codec.html", + "children": [ + { + "title": "编码器", + "url": "/manual/2.3/core/codec.html#编码器", + "content": "编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。USER_NOT_FOUND.code = 10404USER_NOT_FOUND.message = 用户不存在\n\nUSER_LOGIN_FAILURE.code = 10405\nUSER_LOGIN_FAILURE.message = 登录失败\n\n\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(\"USER_NOT_FOUND\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。USER_NOT_FOUND.errorCode = 10404USER_NOT_FOUND.errorMessage = 用户不存在\n\nUSER_LOGIN_FAILURE.errorCode = 10405\nUSER_LOGIN_FAILURE.errorMessage = 登录失败\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(value = \"USER_NOT_FOUND\", code = \"errorCode\", message = \"errorMessage\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/codec.html#编码器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/collect.html", + "children": [ + { + "title": "收集器", + "url": "/manual/2.3/core/collect.html#收集器", + "content": "收集器数组、Map、集合的工具类" + }, + { + "title": "数组", + "url": "/manual/2.3/core/collect.html#收集器-数组", + "content": "数组数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 List、Set 以及字符串类型的数组、数组合并、数组元素操作等方法。检测数组 array 中是否存在元素 element:import com.buession.core.collect.Arrays;\nboolean result = Arrays.contains(array, element);\n返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.indexOf(array, element);\n返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.lastIndexOf(array, element);\n将字符串拼接会字符串:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString result = Arrays.toString(array);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString glue = \"-\";\nString result = Arrays.toString(array, glue);\n// 1-2-3\n可以通过方法 toList、toSet 转换为 List 和 Set:import com.buession.core.collect.Arrays;import java.util.List;\nimport java.util.Set;\n\nint[] array = {1, 2, 3};\nList list = Arrays.toList(array);\nSet set = Arrays.toSet(array);\n将数组转换为字符串类型的数组:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString[] result = Arrays.toStringArray(array);\n将数组进行合并:import com.buession.core.collect.Arrays;\nString[] result = Arrays.toStringArray(array1, array2, array3);\n对数组元素进行操作:import com.buession.core.collect.Arrays;\nString[] array = {\"A\", \"B\", \"C\"};\nString[] result = Arrays.map(array, String.class, fn);\n第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现" + }, + { + "title": "Lists", + "url": "/manual/2.3/core/collect.html#收集器-lists", + "content": "ListsList 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。将字符串拼接会字符串:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString result = Lists.toString(list);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Lists.toString(list);\n// 1-2-3\n可以通过方法 toSet 将 List 转换为 Set:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nSet set = Lists.toSet(list);\n" + }, + { + "title": "Sets", + "url": "/manual/2.3/core/collect.html#收集器-sets", + "content": "SetsSett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。将字符串拼接会字符串:import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString result = Sets.toString(set);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Sets.toString(list);\n// 1-2-3\n可以通过方法 toList 将 Set 转换为 List:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nList list = Sets.toList(set);\n" + }, + { + "title": "Maps", + "url": "/manual/2.3/core/collect.html#收集器-maps", + "content": "MapsMap 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。对 Map 进行操作:import com.buession.core.collect.Maps;import java.util.Map;\nimport java.util.HashMap;\n\nMap maps = new HashMap();\nMap result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());\n第二个、第三参数为 java.util.function.Function 的实现可以通过方法 toList 将 Map 的 value 转换为 List:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\n\nList list = Maps.toList(maps);\n可以通过方法 toSet 将 Map 的 value 转换为 Set:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.Set;\n\nSet set = Maps.toSet(maps);\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/collect.html#收集器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/context.html", + "children": [ + { + "title": "上下文", + "url": "/manual/2.3/core/context.html#上下文", + "content": "上下文注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:逻辑少\n与 Dao 层进行交互,多个 Dao 层的复用\nService 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层\nimport com.buession.core.context.stereotype.Manager;import org.springframework.stereotype.Service;\n\npublic interface UserManager {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Manager\npublic class UserManagerImpl implements UserManager {\n\n\t@Autowired\n\tprivate UserDao userDao;\n\n\t@Autowired\n\tprivate UserProfileDao userProfileDao;\n\n\t@Autowired\n\tprivate RedisTemplate redisTemplate;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = redisTemplate.hGetObject(\"user\", Integer.toString(id), User.class);\n\n\t\tif(user == null){\n\t\t\tuser = userDao.getByPrimary(id);\n\t\t\tif(user != null){\n\t\t\t\tuser.setProfile(userProfileDao.getByUserId(id));\n\t\t\t\tredisTemplate.hSet(\"user\", Integer.toString(id), user);\n\t\t\t}else{\n\t\t\t\tthrow new RuntimeException(\"用户不存在\");\n\t\t\t}\n\t\t}\n\n\t\treturn user;\n\t}\n\n}\n\npublic interface UserService {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Service\npublic class UserServiceImpl implements UserService {\n\n\t@Autowired\n\tprivate UserManager userManager;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = userManager.getByPrimary(id);\n\n\t\t...\n\n\t\treturn user;\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/context.html#上下文-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "", + "content": "", + "url": "/manual/2.3/core/configurer.html", + "children": [ + { + "title": "配置器", + "url": "/manual/2.3/core/configurer.html#配置器", + "content": "配置器使用配置参数对对象进行配置。接口规范。@FunctionalInterfacepublic interface Configurer {\n\n\t/**\n\t * 使用配置参数 config 对对象 object 进行配置\n\t *\n\t * @param object\n\t * \t\t配置对象\n\t * @param config\n\t * \t\t配置参数\n\t */\n\tvoid configure(T object, C config);\n\n}\n示例:public class DefaultConfigurer implements Configurer> {\n\t@Override\n\tpublic void configure(final User user, final Map configs) {\n\t\tuser.setUsername(configs.get(\"name\"));\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/configurer.html#配置器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/customizer.html", + "children": [ + { + "title": "定制器", + "url": "/manual/2.3/core/customizer.html#定制器", + "content": "定制器使用源对象对目标对象进行定制。接口规范。@FunctionalInterfacepublic interface Customizer {\n\n\t/**\n\t * 定制\n\t *\n\t * @param source\n\t * \t\t源实例\n\t * @param target\n\t * \t\t待定制实例\n\t */\n\tvoid customize(S source, T target);\n\n}\n示例:public class DefaultCustomizer implements Customizer {\n\t@Override\n\tpublic void customize(final UserModel userModel, final UserVo userVo) {\n\t\tuserVo.setUsername(userModel.getUsername());\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/customizer.html#定制器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/converter.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.3/core/converter.html#构建器", + "content": "构建器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。接口定义:@FunctionalInterfacepublic interface Converter {\n\n\tT convert(final S source);\n\n}\n将类似为 S 的对象转换为类型为 T 的对象。" + }, + { + "title": "内置转换器", + "url": "/manual/2.3/core/converter.html#构建器-内置转换器", + "content": "内置转换器\n\n转换器\n说明\n\n\n\n\nArrayConverter\n将 S 类型的数组转换为 T 类型的数组\n\n\nEnumConverter\n枚举转换器,将字符串转换为枚举 E\n\n\nBinaryEnumConverter\n枚举转换器,将 byte 数组转换为枚举 E\n\n\nBooleanStatusConverter\n将布尔值转换为 com.buession.lang.Status\n\n\nStatusBooleanConverter\n将 com.buession.lang.Status 转换为布尔值\n\n\nPredicateStatusConverter\n通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status\n\n\nListConverter\n将 S 类型的 List 对象转换为 T 类型的 List 对象\n\n\nSetConverter\n将 S 类型的 Set 对象转换为 T 类型的 Set 对象\n\n\nMapConverter\n将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map\n\n\n将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象import com.buession.core.converter.MapConverter;import java.util.Map;\n\nMap source;\nMap target;\nMapConverter converter = new MapConverter();\n\ntarget = converter.convert(source);\n将字符串转换为枚举import com.buession.core.converter.EnumConverter;import com.buession.lang.Gender;\n\nGender target;\nEnumConverter converter = new EnumConverter(Gender.class);\n\ntarget = converter.convert(\"FEMALE\");\n// Gender.FEMALE\n\ntarget = converter.convert(\"F\");\n// null\n" + }, + { + "title": "POJO 类映射", + "url": "/manual/2.3/core/converter.html#构建器-pojo-类映射", + "content": "POJO 类映射我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。public interface Mapper {\n\t/**\n\t * 将源对象映射到目标对象\n\t *\n\t * @param object\n\t * \t\t源对象\n\t *\n\t * @return 目标对象实例\n\t */\n\tT mapping(S object);\n\n\t/**\n\t * 将源对象数组映射到目标对象数组\n\t *\n\t * @param object\n\t * \t\t源对象数组\n\t *\n\t * @return 目标对象实例数组\n\t */\n\tT[] mapping(S[] object);\n\n\t/**\n\t * 将源 list 对象映射到目标 list 对象\n\t *\n\t * @param object\n\t * \t\t源 list 对象\n\t *\n\t * @return 目标对象 list 实例\n\t */\n\tList mapping(List object);\n\n\t/**\n\t * 将源 set 对象映射到目标 set 对象\n\t *\n\t * @param object\n\t * \t\t源 set 对象\n\t *\n\t * @return 目标对象 set 实例\n\t */\n\tSet mapping(Set object);\n\n}\n我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。import com.buession.core.converter.mapper.PropertyMapper;\nUser source = new User();\nUser target = new User();\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();\npropertyMapper.form(source::getId).to(target:setId)\n// null\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();\npropertyMapper.form(source::getUsername).to(target:setUsername)\n// null\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/converter.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/datetime.html", + "children": [ + { + "title": "日期时间", + "url": "/manual/2.3/core/datetime.html#日期时间", + "content": "日期时间日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。获取当前 Unix 时间戳(秒):import com.buession.core.datetime.DateTime;\nDateTime.unixtime();\n该方法我们在实际业务中经常用到。以 \"msec sec\" 的格式返回当前 Unix 时间戳和微秒数:import com.buession.core.datetime.DateTime;\nDateTime.microtime();\n// 1657171717 948000\n该方法参考 PHP 的 microtime 函数而来。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/datetime.html#日期时间-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/id.html", + "children": [ + { + "title": "ID 生成器", + "url": "/manual/2.3/core/id.html#id-生成器", + "content": "ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。接口规范。public interface IdGenerator {\n\t/**\n\t * 获取下一个 ID\n\t *\n\t * @return ID\n\t */\n\tT nextId();\n\n}\n" + }, + { + "title": "ID 生成器", + "url": "/manual/2.3/core/id.html#id-生成器-id-生成器", + "content": "ID 生成器\n\n生成器\n说明\n\n\n\n\nAtomicSimpleIdGenerator\n基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nAtomicUUIDIdGenerator\n基于 AtomicLong 递增的,UUID ID 生成器\n\n\nNanoIDIdGenerator\njnanoid ID 生成器,可通过构造函数指定字符范围、长度\n\n\nRandomDigitIdGenerator\n随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定\n\n\nRandomIdGenerator\n随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位\n\n\nSimpleIdGenerator\n简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nSnowflakeIdGenerator\n雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值\n\n\nUUIDIdGenerator\nUUID ID 生成器\n\n\nimport com.buession.core.id.AtomicUUIDIdGenerator;import com.buession.core.id.NanoIDIdGenerator;\nimport com.buession.core.id.SnowflakeIdGenerator;\nimport com.buession.core.id.UUIDIdGenerator;\nimport com.buession.core.id.SimpleIdGenerator;\n\nAtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000001\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000002\n\nNanoIDIdGenerator idGenerator = new NanoIDIdGenerator();\nidGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK\n\nSnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();\nidGenerator.nextId(); // 170602258864545792\n\nUUIDIdGenerator idGenerator = new UUIDIdGenerator();\nidGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4\n\nSimpleIdGenerator idGenerator = new SimpleIdGenerator();\nidGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05\n注:关于雪花算法、jnanoid 算法的可自行搜索。\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/id.html#id-生成器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/math.html", + "children": [ + { + "title": "数学函数", + "url": "/manual/2.3/core/math.html#数学函数", + "content": "数学函数定义了实用的数学函数。\n\n方法\n说明\n\n\n\n\ncontinuousSum\n计算两个数之间连续相加之和\n\n\nrangeValue\n获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身\n\n\nimport com.buession.core.math.Math;\nlong result = Math.continuousSum(1, 100);\n// 5050\nimport com.buession.core.math.Math;\nlong value = 3;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 4\nimport com.buession.core.math.Math;\nlong value = 5;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 5\nimport com.buession.core.math.Math;\nlong value = 11;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 10\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/math.html#数学函数-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/serializer.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.3/core/serializer.html#构建器", + "content": "构建器将对象序列化为二进制或 JSON。您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串。" + }, + { + "title": "序列化、反序列化类", + "url": "/manual/2.3/core/serializer.html#构建器-序列化、反序列化类", + "content": "序列化、反序列化类\n\n类\n说明\n\n\n\n\nDefaultByteArraySerializer\n将对象序列化为二进制\n\n\nFastJsonJsonSerializer\n基于 FastJSON 的对象与 JSON 之间的序列化\n\n\nGsonJsonSerializer\n基于 Gson 的对象与 JSON 之间的序列化\n\n\nJacksonJsonSerializer\n基于 Jackson2 的对象与 JSON 之间的序列化\n\n\nDefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码\nFastJsonJsonSerializer、GsonJsonSerializer、JacksonJsonSerializer 可以通过参数 Class、TypeReference 指定返回的对象类型\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/serializer.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/deserializer.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.3/core/deserializer.html#构建器", + "content": "构建器将二进制或 JSON 反序列化为对象。您可以通过该 API,实现将将二进制、JSON 字符串反序列化为对象。" + }, + { + "title": "序列化、反序列化类", + "url": "/manual/2.3/core/deserializer.html#构建器-序列化、反序列化类", + "content": "序列化、反序列化类\n\n类\n说明\n\n\n\n\nDefaultByteArrayDeserializer\n将对象序列化为二进制,或将二进制反序列化为对象\n\n\nFastJsonJsonDeserializer\n基于 FastJSON 的对象与 JSON 之间的序列化和反序列化\n\n\nGsonJsonDeserializer\n基于 Gson 的对象与 JSON 之间的序列化和反序列化\n\n\nJacksonJsonDeserializer\n基于 Jackson2 的对象与 JSON 之间的序列化和反序列化\n\n\nDefaultByteArrayDeserializer 通过 URLDecoder.decode 进行解码成 byte 数组,再反序列化为对象\n在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑\nFastJsonJsonDeserializer、GsonJsonDeserializer、JacksonJsonDeserializer 可以通过参数 Class、TypeReference 指定返回的对象类型\ncom.buession.core.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/deserializer.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/validator.html", + "children": [ + { + "title": "验证器", + "url": "/manual/2.3/core/validator.html#验证器", + "content": "验证器数据验证器及其注解。该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。并提供对应的基于 javax.validation 的校验注解。验证是否为 null判断任意对象是否为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNull(obj);\n验证是否不为 null判断任意对象是否不为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNotNull(obj);\n判断字符串是否为空白字符串判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 falseimport com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\n\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\na\";\nboolean result = Validate.isBlank(str); // false\n注:isNotBlank 与之相反\n判断是否为空isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空import com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isEmpty(str); // true\n\nString str = \" \";\nboolean result = Validate.isEmpty(str); // false\n\nboolean result = Validate.isEmpty(new String[]{}); // true\n\nboolean result = Validate.isEmpty(new Integer[]{1}); // false\n注:isNotEmpty 与之相反\n判断是否在两个数之间isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3); // true\n\nboolean result = Validate.isBetween(2, 2, 3); // false\n可通过参数设置是否包含边界值import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3, true); // true\n\nboolean result = Validate.isBetween(2, 2, 3, true); // true\n判断是否为电话号码isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isTel(\"028-12345678\"); // true\n\nboolean result = Validate.isTel(\"028-02345678\"); // false\n判断是否为手机号码isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isMobile(\"028-12345678\"); // false\n\nboolean result = Validate.isMobile(\"13800138000\"); // true\n判断是否为邮政编码isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。import com.buession.core.validator.Validate;\nboolean result = Validate.isPostCode(\"043104\"); // false\n\nboolean result = Validate.isPostCode(\"643104\"); // true\n判断是否为 QQ 号码isQQ 可判断一个字符串是否为 QQ 号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isQQ(\"043104\"); // false\n\nboolean result = Validate.isQQ(\"25132.141\"); // true\n判断是否为身份证号码isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\", true, \"2.10-01-01\");\n其它,更多方法可以参考手册。" + }, + { + "title": "注解", + "url": "/manual/2.3/core/validator.html#验证器-注解", + "content": "注解javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。\n\n注解\n验证的数据类型\n说明\n\n\n\n\n@Alnum\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Alpha\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Numeric\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Between\nshort、int、double 等任何 Number 的子类型\n验证注解的元素值是否为在两个数之间\n\n\n@Empty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否为空\n\n\n@NotEmpty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否不为空\n\n\n@HasText\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@IDCard\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Ip\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Isbn\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@MimeType\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Mobile\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Null\n任意类型\n验证注解的元素值是否为 null\n\n\n@NotNull\n任意类型\n验证注解的元素值是否为 null\n\n\n@Port\nInteger\n验证注解的元素值是否为 null\n\n\n@PostCode\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@QQ\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Tel\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Xdigit\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/validator.html#验证器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/utils.html", + "children": [ + { + "title": "工具类", + "url": "/manual/2.3/core/utils.html#工具类", + "content": "工具类常用通用性工具类。" + }, + { + "title": "Byte 数组比较", + "url": "/manual/2.3/core/utils.html#工具类-byte-数组比较", + "content": "Byte 数组比较ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。" + }, + { + "title": "注解工具", + "url": "/manual/2.3/core/utils.html#工具类-注解工具", + "content": "注解工具AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class clazz, final Class[] annotations)、hasMethodAnnotationPresent(Method method, final Class[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。" + }, + { + "title": "断言", + "url": "/manual/2.3/core/utils.html#工具类-断言", + "content": "断言Assert 和 springframework 中的注解类似,不过逻辑有些相反。" + }, + { + "title": "Byte 工具", + "url": "/manual/2.3/core/utils.html#工具类-byte-工具", + "content": "Byte 工具ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。import com.buession.core.utils.ByteUtils;\nbyte[] bytes;\nchar c = ByteUtils.toChar(bytes);\n\nchar[] chars = ByteUtils.toChar(bytes);\nbyte 数组连接。import com.buession.core.utils.ByteUtils;\nbyte[] dest;\nbyte[] source\nbyte[] result = ByteUtils.concat(dest, source);\n" + }, + { + "title": "Character 工具", + "url": "/manual/2.3/core/utils.html#工具类-character-工具", + "content": "Character 工具CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。import com.buession.core.utils.CharacterUtils;\nchar c;\nbyte[] bytes = ByteUtils.toBytes(c);\n\nchar[] chars;\nbyte[] bytes = ByteUtils.toBytes(chars);\n" + }, + { + "title": "数字工具", + "url": "/manual/2.3/core/utils.html#工具类-数字工具", + "content": "数字工具NumberUtils 提供了对数字相关的操作。\n\n方法\n说明\n\n\n\n\nint2bytes\n将 int 转换为 byte[]\n\n\nbytes2int\n将 byte[] 转换为 int\n\n\nlong2bytes\n将 long 转换为 byte[]\n\n\nbytes2long\n将 byte[] 转换为 long\n\n\nfloat2bytes\n将 float 转换为 byte[]\n\n\nbytes2float\n将 byte[] 转换为 float\n\n\ndouble2bytes\n将 double 转换为 byte[]\n\n\nbytes2double\n将 byte[] 转换为 double\n\n\n" + }, + { + "title": "字符串工具", + "url": "/manual/2.3/core/utils.html#工具类-字符串工具", + "content": "字符串工具StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。截取字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.substr(\"abcde\", 1); // bcde\nString result = StringUtils.substr(\"abcde\", 1, 2); // bc\n生成随机字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.random(length);\n比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 3); // true\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 4); // false\n忽略大小写比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"Abce\", 3); // true\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"aBce\", 4); // false\n" + }, + { + "title": "拼音工具", + "url": "/manual/2.3/core/utils.html#工具类-拼音工具", + "content": "拼音工具PinyinUtils 封装了获取中文拼音、拼音首字母的方法。import com.buession.core.utils.PinyinUtils;\nString result = PinyinUtils.getPinyin(\"中国\"); // zhongguo\nString result = PinyinUtils.getPinYinFirstChar(\"中国\"); // zg\n" + }, + { + "title": "随机数工具", + "url": "/manual/2.3/core/utils.html#工具类-随机数工具", + "content": "随机数工具RandomUtils 封装了随机数的生成。\n\n方法\n说明\n\n\n\n\nnextBoolean\n随机布尔值\n\n\nnextBytes\n随机字节数组\n\n\nnextInt\n生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE\n\n\nnextLong\n生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE\n\n\nnextFloat\n生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE\n\n\nnextDouble\n生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE\n\n\n" + }, + { + "title": "Properties 工具", + "url": "/manual/2.3/core/utils.html#工具类-properties-工具", + "content": "Properties 工具PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。import com.buession.core.utils.SystemPropertyUtils;\nInteger result = PropertiesUtils.getInteger(properties, key);\nBoolean result = PropertiesUtils.getBoolean(properties, key);\n" + }, + { + "title": "System Property 工具", + "url": "/manual/2.3/core/utils.html#工具类-system-property-工具", + "content": "System Property 工具SystemPropertyUtils 封装了系统属性或系统环境变量的操作。设置属性方法 setProperty 对 System.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty。import com.buession.core.utils.SystemPropertyUtils;\nSystemPropertyUtils.setProperty(\"http.port\", 8080);\nSystemPropertyUtils.setProperty(\"http.ssl.enable\", false);\n获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。String value = System.getProperty(name);\nif(Validate.hasText(value) == false){\n value = System.getenv(name);\n}\n" + }, + { + "title": "版本工具", + "url": "/manual/2.3/core/utils.html#工具类-版本工具", + "content": "版本工具VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。import com.buession.core.utils.VersionUtils;\nVersionUtils.compare(\"1.0.0\", \"1.0.1-beta\"); // -1\nVersionUtils.compare(\"1.0.0\", \"1.0.0r\"); // -1\n规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本获取类的版本值import com.buession.core.utils.VersionUtils;\nByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.3.0\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/utils.html#工具类-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/other.html", + "children": [ + { + "title": "其它", + "url": "/manual/2.3/core/other.html#其它", + "content": "其它通用的接口定义,框架自身类,以及其它杂项。" + }, + { + "title": "框架自身工具", + "url": "/manual/2.3/core/other.html#其它-框架自身工具", + "content": "框架自身工具获取 Buession Framework 版本:import com.buession.core.Framework;import com.buession.core.BuesssionFrameworkVersion;\n\nBuesssionFrameworkVersion.getVersion(); // 2.3.0\nFramework.VERSION; // 2.3.0\n获取 Buession Framework 框架名称:import com.buession.core.Framework;\nFramework.NAME; // \"Buession\"\n" + }, + { + "title": "命令执行器", + "url": "/manual/2.3/core/other.html#其它-命令执行器", + "content": "命令执行器命令执行器接口:/** * 命令执行器\n *\n * @param \n * \t\t命令上下文\n * @param \n * \t\t命令执行返回值\n */\n@FunctionalInterface\npublic interface Executor {\n\n\t/**\n\t * 命令执行\n\t *\n\t * @param context\n\t * \t\t命令执行器上下文\n\t *\n\t * @return 命令执行返回值,R 类型的实例\n\t */\n\tR execute(C context);\n\n}\n您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。" + }, + { + "title": "销毁接口", + "url": "/manual/2.3/core/other.html#其它-销毁接口", + "content": "销毁接口功能类似 java.io.Closeable。public interface Destroyable {\n\t/**\n\t * 销毁相关资源\n\t *\n\t * @throws IOException\n\t * \t\tIO 错误时抛出\n\t */\n\tvoid destroy() throws IOException;\n\n}\n" + }, + { + "title": "Rawable", + "url": "/manual/2.3/core/other.html#其它-rawable", + "content": "Rawable原始的,约定实现该接口的类,必须返回原始字节数组。public interface Rawable {\n\t/**\n\t * 返回原始的字节数组\n\t *\n\t * @return 原始的字节数组\n\t */\n\tbyte[] getRaw();\n\n}\n" + }, + { + "title": "名称节点", + "url": "/manual/2.3/core/other.html#其它-名称节点", + "content": "名称节点名称节点,约定实现该接口的类应该返回一个名称public interface NamedNode {\n\t/**\n\t * 返回节点名称\n\t *\n\t * @return 节点名称\n\t */\n\t@Nullable\n\tString getName();\n\n}\n" + }, + { + "title": "分页", + "url": "/manual/2.3/core/other.html#其它-分页", + "content": "分页com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.3/core/exception.html", + "children": [ + { + "title": "异常", + "url": "/manual/2.3/core/exception.html#异常", + "content": "异常通用异常的定义。\n\n异常\n说明\n\n\n\n\nAccessException\n拒绝访问异常\n\n\nClassInstantiationException\n类实例化异常\n\n\nConversionException\n数据类型转换异常\n\n\nDataAlreadyExistException\n数据已存在异常\n\n\nDataNotFoundException\n数据不存在或未找到异常\n\n\nInsteadException\n类方法废弃后,需要使用其它类库方法来替代\n\n\nNestedRuntimeException\n嵌套运行时异常\n\n\nOperationException\n运算异常\n\n\nPresentException\n--\n\n\nSerializationException\n序列化异常\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/core/exception.html#异常-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-cron 参考手册", + "content": "对 quartz 的二次封装", + "url": "/manual/2.3/cron/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/cron/index.html#安装", + "content": "安装 com.buession\n buession-cron\n x.x.x\n\n由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。且在今后的版本中,该模块可能会被废弃。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/cron/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。", + "url": "/manual/2.3/dao/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/dao/index.html#安装", + "content": "安装 com.buession\n buession-dao\n x.x.x\n\n我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。同时,我们在代码层面实现了数据库的读写分离。我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。" + }, + { + "title": "Dao 接口", + "url": "/manual/2.3/dao/index.html#dao-接口", + "content": "Dao 接口接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.3.0/com/buession/dao/Dao.htmlpublic interface Dao {}\nP:主键类型\nE:实体类\n分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset。条件为 Map 类型,允许为 null。排序为 Map 类型,允许为 null。MyBatisBuession Framework 扩展 MyBatis 的文档。MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/dao/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.3/dao/mybatis.html", + "children": [ + { + "title": "MyBatis", + "url": "/manual/2.3/dao/mybatis.html#mybatis", + "content": "MyBatisBuession Framework 扩展 MyBatis 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.3/dao/mybatis.html#mybatis-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplate、slaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。" + }, + { + "title": "Mybatis 约定", + "url": "/manual/2.3/dao/mybatis.html#mybatis-mybatis-约定", + "content": "Mybatis 约定如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace\nnamespace com.buession.dao.test.dao;\npublic class UserDaoImpl extends AbstractMyBatisDao {\n\n\t@Override\n\tprotected String getStatement(){\n\t\treturn \"com.buession.dao.test.dao.UserMapper\";\n\t}\n\n}\n\n\nMapper 的 SQL ID 和方法名保持一致\n\n\nSQL ID\n说明\n返回值\n\n\n\n\ninsert\n插入数据\n影响的行数\n\n\nbatchInsert\n批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入\n每次插入影响的行数列表\n\n\nreplace\n替换数据,即:REPLACE 语句\n影响的行数\n\n\nbatchReplace\n批量替换数据,即:REPLACE 语句\n每次替换数据影响的行数列表\n\n\nupdate\n更新数据\n更新条数\n\n\nupdateByPrimary\n根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值\n更新条数\n\n\ngetByPrimary\n根据主键查询数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselectOne\n(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselect\n查询数据\n数据结果列表\n\n\ngetAll\n查询所有数据\n数据结果列表\n\n\ncount\n获取记录数\n记录数\n\n\ndeleteByPrimary\n根据主键删除数据\n影响条数\n\n\ndelete\n删除数据\n影响条数\n\n\nclear\n清除数据\n影响条数\n\n\ntruncate\n截断数据\n影响条数\n\n\n注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。\n以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。\n" + }, + { + "title": "Mybatis 类型处理器", + "url": "/manual/2.3/dao/mybatis.html#mybatis-mybatis-类型处理器", + "content": "Mybatis 类型处理器MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao。\n\nTypeHandler\n说明\n\n\n\n\nDefaultEnumTypeHandler\n默认 Enum 类型处理器,将值直接转换为枚举字段\n\n\nIgnoreCaseEnumTypeHandler\n忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段\n\n\nDefaultJsonTypeHandler\nJSON 处理器,将 JSON 格式的字符串值和类型 进行转换\n\n\nDefaultSetEnumTypeHandler\n默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素\n\n\nIgnoreCaseSetEnumTypeHandler\n忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素\n\n\nDefaultSetTypeHandler\n默认 Set 类型处理器,将值以 \",\" 拆分转换为 Set\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/dao/mybatis.html#mybatis-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.3/dao/mongodb.html", + "children": [ + { + "title": "MongoDB", + "url": "/manual/2.3/dao/mongodb.html#mongodb", + "content": "MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.3/dao/mongodb.html#mongodb-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplate、slaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。AbstractMongoDBDao 的 replace 执行的也是 insert。在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/dao/mongodb.html#mongodb-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-geoip 参考手册", + "content": "对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。", + "url": "/manual/2.3/geoip/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/geoip/index.html#安装", + "content": "安装 com.buession\n buession-geoip\n x.x.x\n\n通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。" + }, + { + "title": "获取国家信息", + "url": "/manual/2.3/geoip/index.html#获取国家信息", + "content": "获取国家信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nCountry country = resolver.country(\"114.114.114.114\");\n// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}\n\nCountry country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8\n// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}\n" + }, + { + "title": "获取城市信息", + "url": "/manual/2.3/geoip/index.html#获取城市信息", + "content": "获取城市信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nDistrict district = resolver.district(\"114.114.114.114\");\n// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}\n\nDistrict district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8\n// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}\n" + }, + { + "title": "获取位置信息", + "url": "/manual/2.3/geoip/index.html#获取位置信息", + "content": "获取位置信息位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nLocation location = resolver.location(\"114.114.114.114\");\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Shanghai\",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}\n\nLocation location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Seoul\",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}\n" + }, + { + "title": "缓存", + "url": "/manual/2.3/geoip/index.html#缓存", + "content": "缓存为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。" + }, + { + "title": "Resolver 的 Spring Factory Bean", + "url": "/manual/2.3/geoip/index.html#resolver-的-spring-factory-bean", + "content": "Resolver 的 Spring Factory Bean我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。dbPath 和 stream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。\nenableCache 可以控制是否缓存。\n" + }, + { + "title": "关于 IP 库", + "url": "/manual/2.3/geoip/index.html#关于-ip-库", + "content": "关于 IP 库buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/geoip/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-git 参考手册", + "content": "获取项目 GIT 信息。", + "url": "/manual/2.3/git/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/git/index.html#安装", + "content": "安装 com.buession\n buession-git\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/git/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。", + "url": "/manual/2.3/httpclient/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/httpclient/index.html#安装", + "content": "安装 com.buession\n buession-httpclient\n x.x.x\n\n我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponents 和 okhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。传统的方式: org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\nimport org.apache.http.HttpResponse;import org.apache.http.conn.HttpClientConnectionManager;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.client.methods.HttpPost;\n\nHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();\n\nHttpResponse response = httpClient.execute(new HttpPost(\"https://www.buession.com/\"));\n或者 com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport okhttp3.HttpClientConnectionManager;import okhttp3.OkHttpClient;\nimport okhttp3.ConnectionPool;\nimport okhttp3.Request;\nimport okhttp3.Request.Builder;\nimport okhttp3.Response;\n\nOkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());\nHttpClient httpClient = builder.build();\n\nBuilder requestBuilder = new Builder().post();\nrequestBuilder.url(\"https://www.buession.com/\");\nRequest okHttpRequest = requestBuilder.build();\n\nResponse httpResponse = httpClient.newCall(okHttpRequest).execute();\n现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。 com.buession\n buession-httpclient\n x.x.x\n\n\n org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\n或者 com.buession\n buession-httpclient\n x.x.x\n\n\n com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.OkHttpHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.conn.OkHttpClientConnectionManager;\nimport com.buession.httpclient.core.Response;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/httpclient/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.3/httpclient/configuration.html", + "children": [ + { + "title": "连接配置", + "url": "/manual/2.3/httpclient/configuration.html#连接配置", + "content": "连接配置您可以通过连接配置类 Configuration 配置 apache httpcomponents 和 okhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponents 或 okhttp3 的配置信息。" + }, + { + "title": "配置属性说明", + "url": "/manual/2.3/httpclient/configuration.html#连接配置-配置属性说明", + "content": "配置属性说明\n\n属性名称\n数据类型\napache httpcomponents 对应配置\nokhttp3 对应配置\n默认值\n说明\n\n\n\n\nmaxConnections\nint\nmaxTotal\nmaxIdleConnections\n5000\n最大连接数\n\n\nmaxPerRoute\nint\ndefaultMaxPerRoute\n--\n500\n每个路由的最大连接数\n\n\nidleConnectionTime\nint\ncloseIdleConnections\nkeepAliveDuration\n60000\n空闲连接存活时长(单位:毫秒)\n\n\nconnectTimeout\nint\nconnectTimeout\nconnectTimeout\n3000\n连接超时时间(单位:毫秒)\n\n\nconnectionRequestTimeout\nint\nconnectionRequestTimeout\n--\n5000\n从连接池获取连接的超时时间(单位:毫秒)\n\n\nreadTimeout\nint\nsocketTimeout\nreadTimeout\n5000\n读取超时时间(单位:毫秒)\n\n\nallowRedirects\nBoolean\nredirectsEnabled\nfollowRedirects\n--\n是否允许重定向\n\n\nrelativeRedirectsAllowed\nBoolean\nrelativeRedirectsAllowed\n--\n--\n是否应拒绝相对重定向\n\n\ncircularRedirectsAllowed\nBoolean\ncircularRedirectsAllowed\n--\n--\n是否允许循环重定向\n\n\nmaxRedirects\nInteger\nmaxRedirects\n--\n--\n最大允许重定向次数\n\n\nauthenticationEnabled\nboolean\nauthenticationEnabled\n--\n--\n是否开启 Http Basic 认证\n\n\ncontentCompressionEnabled\nboolean\ncontentCompressionEnabled\n--\n--\n是否启用内容压缩\n\n\nnormalizeUri\nboolean\nnormalizeUri\n--\n--\n是否标准化 URI\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/httpclient/configuration.html#连接配置-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.3/httpclient/connectionmanager.html", + "children": [ + { + "title": "连接管理器", + "url": "/manual/2.3/httpclient/connectionmanager.html#连接管理器", + "content": "连接管理器连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponents 和 okhttp3 的文档。您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponents 或 okhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。" + }, + { + "title": "关于 okhttp 连接管理器", + "url": "/manual/2.3/httpclient/connectionmanager.html#连接管理器-关于-okhttp-连接管理器", + "content": "关于 okhttp 连接管理器okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/httpclient/connectionmanager.html#连接管理器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.3/httpclient/response.html", + "children": [ + { + "title": "响应", + "url": "/manual/2.3/httpclient/response.html#响应", + "content": "响应当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。buession-httpclient 会将 apache httpcomponents 或 okhttp3 的响应对象,转换为 Response。需要注意的是,原生 apache httpcomponents 或 okhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。import com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.core.Response;\nimport java.io.InputStream;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\nInputStream stream = response.getInputStream(); // 以流的形式获取响应体\nString body = response.getBody(); // 以字符串的形式获取响应体\n\nstream.close();\ngetInputStream、getBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponents 或 okhttp3 返回的流。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/httpclient/response.html#响应-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.3/httpclient/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.3/httpclient/method.html#方法", + "content": "方法buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。示例:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\");\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\");\n您可以自定义请求头:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.List;\nimport java.util.ArrayList;\n\nList headers = new ArrayList();\n\nheaders.add(new Header(\"X-SDK-NAME\", \"Buession\"));\nheaders.add(new Header(\"X-Timestamp\", System.currentTimeMillis()));\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", headers);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", headers);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", headers);\n您可以设置请求参数:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.Map;\nimport java.util.HashMap;\n\nMap parameters = new HashMap();\n\nparameters.put(\"action\", \"edit\");\nparameters.put(\"id\", 1);\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", parameters);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", parameters);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", parameters);\n您可以设置请求体:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport jcom.buession.httpclient.core.RequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\n\nEncodedFormRequestBody requestBody = new EncodedFormRequestBody();\n\nrequestBody.addRequestBodyElement(\"username\", \"buession\");\nrequestBody.addRequestBodyElement(\"password\", \"buession\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", requestBody);\n\nJsonRawRequestBody requestBody = new JsonRawRequestBody(new User());\n// PUT 请求\nResponse response = httpClient.put(\"https://www.buession.com/\", requestBody);\n不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody。" + }, + { + "title": "RequestBody", + "url": "/manual/2.3/httpclient/method.html#方法-requestbody", + "content": "RequestBody\n\nRequestBody\nContent-Type\n说明\n\n\n\n\nInputStreamRequestBody\napplication/octet-stream\n二进制请求体\n\n\nChunkedInputStreamRequestBody\napplication/octet-stream\nChunked 二进制请求体\n\n\nRepeatableInputStreamRequestBody\napplication/octet-stream\nRepeatable 二进制请求体\n\n\nEncodedFormRequestBody\napplication/x-www-form-urlencoded\n普通表单请求体\n\n\nMultipartFormRequestBody\nmultipart/form-data\n文件上传表单请求体\n\n\nHtmlRawRequestBody\ntext/html\nHTML 请求体\n\n\nJavaScriptRawRequestBody\napplication/javascript\nJavaScript 请求体\n\n\nJsonRawRequestBody\napplication/json\nJSON 请求体\n\n\nTextRawRequestBody\ntext/plain\nTEXT 请求体\n\n\nXmlRawRequestBody\ntext/xml\nXML 请求体\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/httpclient/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-io 参考手册", + "content": "封装了对文件的操作", + "url": "/manual/2.3/io/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/io/index.html#安装", + "content": "安装 com.buession\n buession-io\n x.x.x\n\n该模块二次封装了 java java.io.File 和 java.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。" + }, + { + "title": "读取文件", + "url": "/manual/2.3/io/index.html#读取文件", + "content": "读取文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nbyte[] result = file.read();\n" + }, + { + "title": "写文件", + "url": "/manual/2.3/io/index.html#写文件", + "content": "写文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nfile.write(\"Buession\");\nfile.write(\"Buession\".getBytes());\nfile.write(\"Buession\", true); // 追加写\n" + }, + { + "title": "获取文件 MD5、SHA-1值", + "url": "/manual/2.3/io/index.html#获取文件-md5、sha-1值", + "content": "获取文件 MD5、SHA-1值import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nString md5 = file.getMd5(); // 获取文件 MD5\nString sha1 = file.getSha1(); // 获取文件 SHA-1\n" + }, + { + "title": "获取文件 MD5、SHA-1 值", + "url": "/manual/2.3/io/index.html#获取文件-md5、sha-1-值", + "content": "获取文件 MD5、SHA-1 值import com.buession.io.file.File;import com.buession.io.MimeType;\n\nFile file = new File(\"/tmp/debug.txt\");\n\nMimeType result = file.getMimeType();\n" + }, + { + "title": "设置文件权限", + "url": "/manual/2.3/io/index.html#设置文件权限", + "content": "设置文件权限import com.buession.io.file.Files;\nFiles.chmod(\"/tmp/debug.txt\", 0777);\n" + }, + { + "title": "设置文件用户组", + "url": "/manual/2.3/io/index.html#设置文件用户组", + "content": "设置文件用户组import com.buession.io.file.Files;\nFiles.chgrp(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "设置文件用户", + "url": "/manual/2.3/io/index.html#设置文件用户", + "content": "设置文件用户import com.buession.io.file.Files;\nFiles.chown(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "注解", + "url": "/manual/2.3/io/index.html#注解", + "content": "注解注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。import com.buession.io.json.annotation.MimeTypeString;\nclass File {\n\n @MimeTypeString\n private MimeType mime;\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/io/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-jdbc 参考手册", + "content": "JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。", + "url": "/manual/2.3/jdbc/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/jdbc/index.html#安装", + "content": "安装 com.buession\n buession-jdbc\n x.x.x\n\n通过提供的 API,您可以简化对 DBCP2、Druid、Hikari、Tomcat 数据源的初始化,该类库基本不单独使用。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/jdbc/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-json 参考手册", + "content": "主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。", + "url": "/manual/2.3/json/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/json/index.html#安装", + "content": "安装 com.buession\n buession-json\n x.x.x\n\n封装了大量基于 jackson 的注解。" + }, + { + "title": "注解", + "url": "/manual/2.3/json/index.html#注解", + "content": "注解\n\n注解\n说明\n\n\n\n\nCalendarUnixTimestamp\njava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar\n\n\nDateUnixTimestamp\njava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date\n\n\nSqlDateUnixTimestamp\njava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date\n\n\nTimestampUnixTimestamp\njava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp\n\n\nJsonEnum2Map\n枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举\n\n\nSensitive\n通过该注解可以实现数据的脱敏\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/json/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-lang 参考手册", + "content": "常用 POJO 类和枚举的定义,详细查看 API 参考手册。", + "url": "/manual/2.3/lang/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/lang/index.html#安装", + "content": "安装 com.buession\n buession-lang\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/lang/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-net 参考手册", + "content": "网络相关工具类。", + "url": "/manual/2.3/net/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/net/index.html#安装", + "content": "安装 com.buession\n buession-net\n x.x.x\n\n" + }, + { + "title": "IP 地址工具类", + "url": "/manual/2.3/net/index.html#ip-地址工具类", + "content": "IP 地址工具类IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。import com.buession.net.utils.InetAddressUtis;\nlong result = InetAddressUtis.ip2long(\"127.0.0.1\"); // 2130706433\nString ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1\nURI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/net/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。", + "url": "/manual/2.3/redis/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/redis/index.html#安装", + "content": "安装 com.buession\n buession-redis\n x.x.x\n\n" + }, + { + "title": "介绍", + "url": "/manual/2.3/redis/index.html#介绍", + "content": "介绍buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。import com.buession.redis.RedisTemplate;import com.buession.redis.core.Options;\nimport com.buession.core.serializer.type.TypeReference;\nimport java.utils.Map;\nimport java.utils.HashMap;\n\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.setOptions(new Options());\nredisTemplate.afterPropertiesSet();\n\n// 将 User 对象写进 key 为 user hash 中\nredisTemplate.hSet(\"user\", \"1\", new User());\n\n// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User\nUser user = redisTemplate.hGetObject(\"user\", \"1\", User.class);\n\n// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User\nMap data = redisTemplate.hGetAllObject(\"user\", \"1\", new TypeReference>{});\n" + }, + { + "title": "展望", + "url": "/manual/2.3/redis/index.html#展望", + "content": "展望目前,buession-redis 仅支持 jedis,不支持 lettuce,我们计划在 2.3 ~ 2.5 的版本中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/redis/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.3/redis/datasource.html", + "children": [ + { + "title": "数据源", + "url": "/manual/2.3/redis/datasource.html#数据源", + "content": "数据源buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。数据源 DataSource 包括三个子接口:StandaloneDataSource:单机模式数据源\nSentinelDataSource:哨兵模式数据源\nClusterDataSource:集群模式数据源\njedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。测试环境 properties:redis.host=127.0.0.1redis.port=6379\nredis.password=\n生产环境 properties:redis.host=192.168.100.131redis.port=6379\nredis.password=passwd\n" + }, + { + "title": "连接池", + "url": "/manual/2.3/redis/datasource.html#数据源-连接池", + "content": "连接池通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfig 和 ConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。连接池配置\n\n配置项\n数据类型\n-- 默认值\n说明\n\n\n\n\nlifo\nboolean\nGenericObjectPoolConfig.DEFAULT_LIFO\n池模式,为 true 时,后进先出;为 false 时,先进先出\n\n\nfairness\nboolean\nGenericObjectPoolConfig.DEFAULT_FAIRNESS\n当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制\n\n\nmaxWait\nDuration\nGenericObjectPoolConfig.DEFAULT_MAX_WAIT\n当连接池资源用尽后,调用者获取连接时的最大等待时间\n\n\nminEvictableIdleTime\nDuration\n60000\n连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除\n\n\nsoftMinEvictableIdleTime\nDuration\nGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION\n连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数\n\n\nevictionPolicyClassName\nString\nGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME\n驱逐策略的类名\n\n\nevictorShutdownTimeout\nDuration\nGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT\n关闭驱逐线程的超时时间\n\n\nnumTestsPerEvictionRun\nint\n-1\n检测空闲对象线程每次运行时检测的空闲对象的数量\n\n\ntestOnCreate\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE\n在创建对象时检测对象是否有效,配置 true 会降低性能\n\n\ntestOnBorrow\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW\n在从对象池获取对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestOnReturn\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN\n在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestWhileIdle\nboolean\ntrue\n在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\nblockWhenExhausted\nboolean\nGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED\n当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\njmxEnabled\nboolean\nGenericObjectPoolConfig.DEFAULT_JMX_ENABLE\n是否注册 JMX\n\n\njmxNamePrefix\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX\nJMX 前缀\n\n\njmxNameBase\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE\n使用 base + jmxNamePrefix + i 来生成 ObjectName\n\n\nmaxTotal\nint\nGenericObjectPoolConfig.DEFAULT_MAX_TOTAL\n最大连接数\n\n\nminIdle\nint\nGenericObjectPoolConfig.DEFAULT_MIN_IDLE\n最小空闲连接数\n\n\nmaxIdle\nint\nGenericObjectPoolConfig.DEFAULT_MAX_IDLE\n最大空闲连接数\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/redis/datasource.html#数据源-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.3/redis/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.3/redis/method.html#方法", + "content": "方法buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。import com.buession.redis.BaseRedisTemplate;\nBaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 删除哈希表 key 中的一个或多个指定域\nredisTemplate.hDel(\"user\", \"1\", \"2\", \"3\");\n\n// 检查给定 key 是否存在\nredisTemplate.exists(\"user\");\n\n// 获取列表 key 中,下标为 index 的元素\nredisTemplate.lIndex(\"user\", 1);\n\n// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾\nredisTemplate.append(\"key\", \"value 1\");\nBaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。import com.buession.redis.RedisTemplate;\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类\nUser user = redisTemplate.lIndexObject(\"user\", 1, User.class);\n序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/redis/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-session 参考手册", + "content": "无文档", + "url": "/manual/2.3/session/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/session/index.html#安装", + "content": "安装 com.buession\n buession-session\n x.x.x\n\n该模块无实际意义,将在今后的版本中会删除掉。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/session/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-thesaurus 参考手册", + "content": "对词库的解析,目前仅支持搜狗词条。", + "url": "/manual/2.3/thesaurus/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/thesaurus/index.html#安装", + "content": "安装 com.buession\n buession-thesaurus\n x.x.x\n\n您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。import com.buession.thesaurus.SogouParser;import com.buession.thesaurus.Parser;\nimport com.buession.thesaurus.core.Word;\nimport java.util.Set;\n\nParser parser = new SogouParser();\n\nSet words parser.parse(\"搜谱拼音词条文件路径\");\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/thesaurus/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-velocity 参考手册", + "content": "spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。", + "url": "/manual/2.3/velocity/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/velocity/index.html#安装", + "content": "安装 com.buession\n buession-velocity\n x.x.x\n\n该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/velocity/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。", + "url": "/manual/2.3/web/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.3/web/index.html#安装", + "content": "安装 com.buession\n buession-web\n x.x.x\n\nbuession-web 扩展了 spring-webmvc、spring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.3/web/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/annotation.html", + "children": [ + { + "title": "注解", + "url": "/manual/2.3/web/annotation.html#注解", + "content": "注解我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。" + }, + { + "title": "注解", + "url": "/manual/2.3/web/annotation.html#注解-注解", + "content": "注解\n\n注解\nRequest / Response\n作用域\n说明\n\n\n\n\n@RequestClientIp\nrequest\n方法参数\n获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress\n\n\n@ContentType\nresponse\n类、方法\n设置响应 Content-Type\n\n\n@HttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值\n\n\n@DisableHttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存\n\n\n@ResponseHeader\nresponse\n类、方法\n设置响应头\n\n\n@ResponseHeaders\nresponse\n类、方法\n批量设置响应头\n\n\n@DocumentMetaData\nresponse\n类、方法\n设置页面标题、页面编码、关键字、描述、版权等等元信息\n\n\n获取用户端真实 IP@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n设置页面缓存@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n以上,会自动计算 Cache-Control 和 pragma 的值。当然,您也可以手动指定。@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\", cacheControl=\"public, max-age=5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/filter.html", + "children": [ + { + "title": "过滤器", + "url": "/manual/2.3/web/filter.html#过滤器", + "content": "过滤器我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。" + }, + { + "title": "过滤器", + "url": "/manual/2.3/web/filter.html#过滤器-过滤器", + "content": "过滤器\n\n过滤器\n说明\n\n\n\n\nPoweredByFilter\nPowered By 过滤器\n\n\nPrintUrlFilter\n打印当前请求 URL 过滤器\n\n\nResponseHeaderFilter\n响应头过滤器,设置响应头\n\n\nResponseHeadersFilter\n响应头过滤器,批量设置响应头\n\n\nServerInfoFilter\nServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点\n\n\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/response.html", + "children": [ + { + "title": "Response", + "url": "/manual/2.3/web/response.html#response", + "content": "Response我们定义了 restful 返回数据类型。public class Response {\n\t/**\n\t * 状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t */\n\tprivate boolean state;\n\n\t/**\n\t * 错误状态码\n\t */\n\tprivate int code;\n\n\t/**\n\t * 提示或错误消息\n\t */\n\tprivate String message;\n\n\t/**\n\t * 数据\n\t */\n\tprivate E data;\n\n\t/**\n\t * 分页对象\n\t */\n\tprivate Pagination pagination;\n\n\t/**\n\t * 构造函数\n\t */\n\tpublic Response();\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t */\n\tpublic Response(boolean state);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param code\n\t * \t\t错误码\n\t */\n\tpublic Response(boolean state, int code);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param code\n\t * \t\t错误码\n\t * @param message\n\t * \t\t提示或错误消息\n\t */\n\tpublic Response(boolean state, int code, String message);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param message\n\t * \t\t提示或错误消息\n\t */\n\tpublic Response(boolean state, String message);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param code\n\t * \t\t错误码\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t */\n\tpublic Response(boolean state, int code, String message, E data);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t */\n\tpublic Response(boolean state, String message, E data);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param code\n\t * \t\t错误码\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t * @param pagination\n\t * \t\t分页对象\n\t */\n\tpublic Response(boolean state, int code, String message, E data, Pagination pagination);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param code\n\t * \t\t错误码\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t * @param pagination\n\t * \t\t分页对象\n\t */\n\t@Deprecated\n\tpublic Response(boolean state, int code, String message, E data, com.buession.core.Pagination pagination) ;\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t * @param pagination\n\t * \t\t分页对象\n\t */\n\tpublic Response(boolean state, String message, E data, Pagination pagination);\n\n\t/**\n\t * 构造函数\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t * @param message\n\t * \t\t提示或错误消息\n\t * @param data\n\t * \t\t数据\n\t * @param pagination\n\t * \t\t分页对象\n\t */\n\t@Deprecated\n\tpublic Response(boolean state, String message, E data, com.buession.core.Pagination pagination);\n\n\t/**\n\t * 返回状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t *\n\t * @return 状态\n\t */\n\tpublic boolean isState();\n\n\t/**\n\t * 返回状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t *\n\t * @return 状态\n\t */\n\tpublic boolean getState();\n\n\t/**\n\t * 设置状态\n\t *\n\t * @param state\n\t * \t\t状态,true 为逻辑成功;false 为逻辑失败或出现异常\n\t */\n\tpublic void setState(boolean state);\n\n\t/**\n\t * 返回错误码\n\t *\n\t * @return 错误码\n\t */\n\tpublic int getCode();\n\n\t/**\n\t * 设置错误码\n\t *\n\t * @param code\n\t * \t\t错误码\n\t */\n\tpublic void setCode(int code);\n\n\t/**\n\t * 返回提示或错误消息\n\t *\n\t * @return 提示或错误消息\n\t */\n\tpublic String getMessage();\n\n\t/**\n\t * 设置提示或错误消息\n\t *\n\t * @param message\n\t * \t\t提示或错误消息\n\t */\n\tpublic void setMessage(String message);\n\n\t/**\n\t * 返回数据\n\t *\n\t * @return 数据\n\t */\n\tpublic E getData();\n\n\t/**\n\t * 设置数据\n\t *\n\t * @param data\n\t * \t\t数据\n\t */\n\tpublic void setData(E data);\n\n\t/**\n\t * 返回分页对象\n\t *\n\t * @return 分页对象\n\t */\n\tpublic Pagination getPagination();\n\n\t/**\n\t * 设置分页对象\n\t *\n\t * @param pagination\n\t * \t\t分页对象\n\t */\n\tpublic void setPagination(Pagination pagination);\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/pagination.html", + "children": [ + { + "title": "Response", + "url": "/manual/2.3/web/pagination.html#response", + "content": "Response我们定义了 restful 返回数据类型中的分页对象。public class Pagination {\n\t/**\n\t * 默认每页大小\n\t */\n\tpublic final static int PAGESIZE = 15;\n\n\t/**\n\t * 当前页码\n\t */\n\tprivate int page = 1;\n\n\t/**\n\t * 每页大小\n\t */\n\tprivate int pagesize = PAGESIZE;\n\n\t/**\n\t * 前一页页码\n\t */\n\tprivate int previousPage = 1;\n\n\t/**\n\t * 下一页页码\n\t */\n\tprivate int nextPage = 1;\n\n\t/**\n\t * 总页码\n\t */\n\tprivate int totalPages = 1;\n\n\t/**\n\t * 总记录数\n\t */\n\tprivate long totalRecords = 0;\n\n\t/**\n\t * Constructs with default configuration.\n\t */\n\tpublic Pagination();\n\n\t/**\n\t * Constructs with page and pagesize.\n\t *\n\t * @param page\n\t * \t\t当前页码\n\t * @param pagesize\n\t * \t\t每页大小\n\t */\n\tpublic Pagination(int page, int pagesize);\n\n\t/**\n\t * Constructs with page, pagesize and totalRecords.\n\t *\n\t * @param page\n\t * \t\t当前页码\n\t * @param pagesize\n\t * \t\t每页大小\n\t * @param totalRecords\n\t * \t\t总记录数\n\t */\n\tpublic Pagination(int page, int pagesize, long totalRecords);\n\n\t/**\n\t * 返回当前页码\n\t *\n\t * @return 当前页码\n\t */\n\tpublic int getPage();\n\n\t/**\n\t * 设置当前页码\n\t *\n\t * @param page\n\t * \t\t当前页码\n\t */\n\tpublic void setPage(int page);\n\n\t/**\n\t * 返回每页大小\n\t *\n\t * @return 每页大小\n\t */\n\tpublic int getPagesize();\n\n\t/**\n\t * 设置每页大小\n\t *\n\t * @param pagesize\n\t * \t\t每页大小\n\t */\n\tpublic void setPagesize(int pagesize);\n\n\t/**\n\t * 返回前一页页码\n\t *\n\t * @return 前一页页码\n\t */\n\tpublic int getPreviousPage();\n\n\t/**\n\t * 设置前一页页码\n\t *\n\t * @param previousPage\n\t * \t\t前一页页码\n\t */\n\tpublic void setPreviousPage(int previousPage);\n\n\t/**\n\t * 返回下一页页码\n\t *\n\t * @return 下一页页码\n\t */\n\tpublic int getNextPage(){\n\t\treturn nextPage;\n\t}\n\n\t/**\n\t * 设置下一页页码\n\t *\n\t * @param nextPage\n\t * \t\t下一页页码\n\t */\n\tpublic void setNextPage(int nextPage);\n\n\t/**\n\t * 返回总页码\n\t *\n\t * @return 总页码\n\t */\n\tpublic int getTotalPages();\n\n\t/**\n\t * 设置总页码\n\t *\n\t * @param totalPages\n\t * \t\t总页码\n\t */\n\tpublic void setTotalPages(int totalPages);\n\n\t/**\n\t * 返回总记录数\n\t *\n\t * @return 总记录数\n\t */\n\tpublic long getTotalRecords();\n\n\t/**\n\t * 设置总记录数\n\t *\n\t * @param totalRecords\n\t * \t\t总记录数\n\t */\n\tpublic void setTotalRecords(long totalRecords);\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/restful.html", + "children": [ + { + "title": "RESTFUL", + "url": "/manual/2.3/web/restful.html#restful", + "content": "RESTFULRestful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。@RestController@RequestMapping(path = \"/example\")\npublic class ExampleController extends AbstractRestController {\n\n\t@Override\n\tpublic Response add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){\n\t\t\n\t}\n\n\t@Override\n\tpublic Response edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id, @RequestBody ExampleDto example){\n\n\t}\n\n\t@Override\n\tpublic Response detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\n\t}\n\n\t@Override\n\tpublic Response delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.3/web/utils.html", + "children": [ + { + "title": "工具", + "url": "/manual/2.3/web/utils.html#工具", + "content": "工具我们封装了一些 web 相关的工具类,用于处理 request、response。servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。获取客户端真实 IP 地址:RequestUtils.getClientIp(request);我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr是否是 Ajax 请求:RequestUtils.isAjaxRequest(request);是否是移动设备请求:RequestUtils.isMobile(request);设置缓存:ResponseUtils.httpCache(response, 5); // 缓存 5 秒ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点\n" + } + ] + }, + { + "title": "API 参考手册", + "content": "Buession Framework API 包含以下目录:\n\n模块\n使用帮助\n手册\n\n\n\n\nbuession-aop\n使用帮助\nAPI 手册\n\n\nbuession-beans\n使用帮助\nAPI 手册\n\n\nbuession-core\n使用帮助\nAPI 手册\n\n\nbuession-cron\n使用帮助\nAPI 手册\n\n\nbuession-dao\n使用帮助\nAPI 手册\n\n\nbuession-geoip\n使用帮助\nAPI 手册\n\n\nbuession-httpclient\n使用帮助\nAPI 手册\n\n\nbuession-io\n使用帮助\nAPI 手册\n\n\nbuession-jdbc\n使用帮助\nAPI 手册\n\n\nbuession-json\n使用帮助\nAPI 手册\n\n\nbuession-lang\n使用帮助\nAPI 手册\n\n\nbuession-net\n使用帮助\nAPI 手册\n\n\nbuession-redis\n使用帮助\nAPI 手册\n\n\nbuession-session\n使用帮助\nAPI 手册\n\n\nbuession-thesaurus\n使用帮助\nAPI 手册\n\n\nbuession-velocity\n使用帮助\nAPI 手册\n\n\nbuession-web\n使用帮助\nAPI 手册\n\n\n", + "url": "/manual/2.2/index.html", + "children": [] + }, + { + "title": "buession-aop 参考手册", + "content": "AOP 封装,方便实现自定义注解", + "url": "/manual/2.2/aop/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/aop/index.html#安装", + "content": "安装 com.buession\n buession-aop\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/aop/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-beans 参考手册", + "content": "该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。", + "url": "/manual/2.2/beans/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/beans/index.html#安装", + "content": "安装 com.buession\n buession-beans\n x.x.x\n\n" + }, + { + "title": "属性拷贝", + "url": "/manual/2.2/beans/index.html#属性拷贝", + "content": "属性拷贝使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。import com.buession.beans.BeanUtils;\nBeanUtils.copyProperties(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.copyProperties(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "属性映射", + "url": "/manual/2.2/beans/index.html#属性映射", + "content": "属性映射使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。import com.buession.beans.BeanUtils;\nBeanUtils.populate(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.populate(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "Bean 转换为 Map", + "url": "/manual/2.2/beans/index.html#bean-转换为-map", + "content": "Bean 转换为 Map使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Keyimport com.buession.beans.BeanUtils;\nMap result = BeanUtils.toMap(bean)\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/beans/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。", + "url": "/manual/2.2/core/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/core/index.html#安装", + "content": "安装 com.buession\n buession-core\n x.x.x\n\n构建器Map、集合的便捷式构建,减少您的代码行数编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中收集器数组、Map、集合的工具类上下文定义应用上下文的类库、注解转换器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。日期时间日期、时间工具ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。数学函数定义了实用的数学函数序列化和反序列化对象的序列化和反序列化,包括二进制和 JSON。验证器数据验证器及其注解工具类常用通用性工具类其它通用的接口定义,框架自身类异常通用异常的定义" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/builder.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.2/core/builder.html#构建器", + "content": "构建器Map、集合的便捷式构建,减少您的代码行数。您需要往 Map、List 中添加元素的传统写法是:import java.util.ArrayList;import java.util.List;\nimport java.util.HashMap;\nimport java.util.Map;\n\nList list = new ArrayList();\nlist.add(\"A\");\nlist.add(\"B\");\nlist.add(\"C\");\n\nMap map = new HashMap();\nmap.put(\"a\", \"A\");\nmap.put(\"b\", \"B\");\nmap.put(\"c\", \"C\");\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.Map;\n\nList list = ListBuilder.create().add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create().put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.LinkedHashMap;\n\nList list = ListBuilder.create(LinkedList.class).add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create(LinkedHashMap.class).put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数\n当您有 value 为 null 时,不添加到 List 时,传统写法:import java.util.ArrayList;import java.util.List;\n\nString value = null;\nList list = new ArrayList();\n\nif(value != null){\n\tlist.add(value);\n}\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import java.util.List;\n\nString value = null;\nList list = ListBuilder.create().addIfPresent(value).build();\nMap、Set、Queue 同理。" + }, + { + "title": "便捷方法", + "url": "/manual/2.2/core/builder.html#构建器-便捷方法", + "content": "便捷方法\n\n方法\n说明\n\n\n\n\n List ListBuilder.epmty()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of(V value)\n创建仅有一个元素的 V 类型的 List 对象\n\n\n Queue QueueBuilder.epmty()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Queue 对象\n\n\n Set SetBuilder.epmty()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Set 对象\n\n\n Map MapBuilder.epmty()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of(V value)\n创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\nempty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/builder.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/codec.html", + "children": [ + { + "title": "编码器", + "url": "/manual/2.2/core/codec.html#编码器", + "content": "编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。USER_NOT_FOUND.code = 10404USER_NOT_FOUND.message = 用户不存在\n\nUSER_LOGIN_FAILURE.code = 10405\nUSER_LOGIN_FAILURE.message = 登录失败\n\n\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(\"USER_NOT_FOUND\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。USER_NOT_FOUND.errorCode = 10404USER_NOT_FOUND.errorMessage = 用户不存在\n\nUSER_LOGIN_FAILURE.errorCode = 10405\nUSER_LOGIN_FAILURE.errorMessage = 登录失败\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(value = \"USER_NOT_FOUND\", code = \"errorCode\", message = \"errorMessage\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/codec.html#编码器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/collect.html", + "children": [ + { + "title": "收集器", + "url": "/manual/2.2/core/collect.html#收集器", + "content": "收集器数组、Map、集合的工具类" + }, + { + "title": "数组", + "url": "/manual/2.2/core/collect.html#收集器-数组", + "content": "数组数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 List、Set 以及字符串类型的数组、数组合并、数组元素操作等方法。检测数组 array 中是否存在元素 element:import com.buession.core.collect.Arrays;\nboolean result = Arrays.contains(array, element);\n返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.indexOf(array, element);\n返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.lastIndexOf(array, element);\n将字符串拼接会字符串:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString result = Arrays.toString(array);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString glue = \"-\";\nString result = Arrays.toString(array, glue);\n// 1-2-3\n可以通过方法 toList、toSet 转换为 List 和 Set:import com.buession.core.collect.Arrays;import java.util.List;\nimport java.util.Set;\n\nint[] array = {1, 2, 3};\nList list = Arrays.toList(array);\nSet set = Arrays.toSet(array);\n将数组转换为字符串类型的数组:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString[] result = Arrays.toStringArray(array);\n将数组进行合并:import com.buession.core.collect.Arrays;\nString[] result = Arrays.toStringArray(array1, array2, array3);\n对数组元素进行操作:import com.buession.core.collect.Arrays;\nString[] array = {\"A\", \"B\", \"C\"};\nString[] result = Arrays.map(array, String.class, fn);\n第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现" + }, + { + "title": "Lists", + "url": "/manual/2.2/core/collect.html#收集器-lists", + "content": "ListsList 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。将字符串拼接会字符串:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString result = Lists.toString(list);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Lists.toString(list);\n// 1-2-3\n可以通过方法 toSet 将 List 转换为 Set:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nSet set = Lists.toSet(list);\n" + }, + { + "title": "Sets", + "url": "/manual/2.2/core/collect.html#收集器-sets", + "content": "SetsSett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。将字符串拼接会字符串:import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString result = Sets.toString(set);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Sets.toString(list);\n// 1-2-3\n可以通过方法 toList 将 Set 转换为 List:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nList list = Sets.toList(set);\n" + }, + { + "title": "Maps", + "url": "/manual/2.2/core/collect.html#收集器-maps", + "content": "MapsMap 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。对 Map 进行操作:import com.buession.core.collect.Maps;import java.util.Map;\nimport java.util.HashMap;\n\nMap maps = new HashMap();\nMap result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());\n第二个、第三参数为 java.util.function.Function 的实现可以通过方法 toList 将 Map 的 value 转换为 List:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\n\nList list = Maps.toList(maps);\n可以通过方法 toSet 将 Map 的 value 转换为 Set:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.Set;\n\nSet set = Maps.toSet(maps);\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/collect.html#收集器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/context.html", + "children": [ + { + "title": "上下文", + "url": "/manual/2.2/core/context.html#上下文", + "content": "上下文注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:逻辑少\n与 Dao 层进行交互,多个 Dao 层的复用\nService 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层\nimport com.buession.core.context.stereotype.Manager;import org.springframework.stereotype.Service;\n\npublic interface UserManager {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Manager\npublic class UserManagerImpl implements UserManager {\n\n\t@Autowired\n\tprivate UserDao userDao;\n\n\t@Autowired\n\tprivate UserProfileDao userProfileDao;\n\n\t@Autowired\n\tprivate RedisTemplate redisTemplate;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = redisTemplate.hGetObject(\"user\", Integer.toString(id), User.class);\n\n\t\tif(user == null){\n\t\t\tuser = userDao.getByPrimary(id);\n\t\t\tif(user != null){\n\t\t\t\tuser.setProfile(userProfileDao.getByUserId(id));\n\t\t\t\tredisTemplate.hSet(\"user\", Integer.toString(id), user);\n\t\t\t}else{\n\t\t\t\tthrow new RuntimeException(\"用户不存在\");\n\t\t\t}\n\t\t}\n\n\t\treturn user;\n\t}\n\n}\n\npublic interface UserService {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Service\npublic class UserServiceImpl implements UserService {\n\n\t@Autowired\n\tprivate UserManager userManager;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = userManager.getByPrimary(id);\n\n\t\t...\n\n\t\treturn user;\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/context.html#上下文-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/converter.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.2/core/converter.html#构建器", + "content": "构建器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。接口定义:@FunctionalInterfacepublic interface Converter {\n\n\tT convert(final S source);\n\n}\n将类似为 S 的对象转换为类型为 T 的对象。" + }, + { + "title": "内置转换器", + "url": "/manual/2.2/core/converter.html#构建器-内置转换器", + "content": "内置转换器\n\n转换器\n说明\n\n\n\n\nArrayConverter\n将 S 类型的数组转换为 T 类型的数组\n\n\nEnumConverter\n枚举转换器,将字符串转换为枚举 E\n\n\nBinaryEnumConverter\n枚举转换器,将 byte 数组转换为枚举 E\n\n\nBooleanStatusConverter\n将布尔值转换为 com.buession.lang.Status\n\n\nStatusBooleanConverter\n将 com.buession.lang.Status 转换为布尔值\n\n\nPredicateStatusConverter\n通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status\n\n\nListConverter\n将 S 类型的 List 对象转换为 T 类型的 List 对象\n\n\nSetConverter\n将 S 类型的 Set 对象转换为 T 类型的 Set 对象\n\n\nMapConverter\n将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map\n\n\n将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象import com.buession.core.converter.MapConverter;import java.util.Map;\n\nMap source;\nMap target;\nMapConverter converter = new MapConverter();\n\ntarget = converter.convert(source);\n将字符串转换为枚举import com.buession.core.converter.EnumConverter;import com.buession.lang.Gender;\n\nGender target;\nEnumConverter converter = new EnumConverter(Gender.class);\n\ntarget = converter.convert(\"FEMALE\");\n// Gender.FEMALE\n\ntarget = converter.convert(\"F\");\n// null\n" + }, + { + "title": "POJO 类映射", + "url": "/manual/2.2/core/converter.html#构建器-pojo-类映射", + "content": "POJO 类映射我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。public interface Mapper {\n\t/**\n\t * 将源对象映射到目标对象\n\t *\n\t * @param object\n\t * \t\t源对象\n\t *\n\t * @return 目标对象实例\n\t */\n\tT mapping(S object);\n\n\t/**\n\t * 将源对象数组映射到目标对象数组\n\t *\n\t * @param object\n\t * \t\t源对象数组\n\t *\n\t * @return 目标对象实例数组\n\t */\n\tT[] mapping(S[] object);\n\n\t/**\n\t * 将源 list 对象映射到目标 list 对象\n\t *\n\t * @param object\n\t * \t\t源 list 对象\n\t *\n\t * @return 目标对象 list 实例\n\t */\n\tList mapping(List object);\n\n\t/**\n\t * 将源 set 对象映射到目标 set 对象\n\t *\n\t * @param object\n\t * \t\t源 set 对象\n\t *\n\t * @return 目标对象 set 实例\n\t */\n\tSet mapping(Set object);\n\n}\n我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。import com.buession.core.converter.mapper.PropertyMapper;\nUser source = new User();\nUser target = new User();\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();\npropertyMapper.form(source::getId).to(target:setId)\n// null\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();\npropertyMapper.form(source::getUsername).to(target:setUsername)\n// null\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/converter.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/datetime.html", + "children": [ + { + "title": "日期时间", + "url": "/manual/2.2/core/datetime.html#日期时间", + "content": "日期时间日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。获取当前 Unix 时间戳(秒):import com.buession.core.datetime.DateTime;\nDateTime.unixtime();\n该方法我们在实际业务中经常用到。以 \"msec sec\" 的格式返回当前 Unix 时间戳和微秒数:import com.buession.core.datetime.DateTime;\nDateTime.microtime();\n// 1657171717 948000\n该方法参考 PHP 的 microtime 函数而来。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/datetime.html#日期时间-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/id.html", + "children": [ + { + "title": "ID 生成器", + "url": "/manual/2.2/core/id.html#id-生成器", + "content": "ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。接口规范。public interface IdGenerator {\n\t/**\n\t * 获取下一个 ID\n\t *\n\t * @return ID\n\t */\n\tT nextId();\n\n}\n" + }, + { + "title": "ID 生成器", + "url": "/manual/2.2/core/id.html#id-生成器-id-生成器", + "content": "ID 生成器\n\n生成器\n说明\n\n\n\n\nAtomicSimpleIdGenerator\n基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nAtomicUUIDIdGenerator\n基于 AtomicLong 递增的,UUID ID 生成器\n\n\nNanoIDIdGenerator\njnanoid ID 生成器,可通过构造函数指定字符范围、长度\n\n\nRandomDigitIdGenerator\n随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定\n\n\nRandomIdGenerator\n随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位\n\n\nSimpleIdGenerator\n简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nSnowflakeIdGenerator\n雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值\n\n\nUUIDIdGenerator\nUUID ID 生成器\n\n\nimport com.buession.core.id.AtomicUUIDIdGenerator;import com.buession.core.id.NanoIDIdGenerator;\nimport com.buession.core.id.SnowflakeIdGenerator;\nimport com.buession.core.id.UUIDIdGenerator;\nimport com.buession.core.id.SimpleIdGenerator;\n\nAtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000001\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000002\n\nNanoIDIdGenerator idGenerator = new NanoIDIdGenerator();\nidGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK\n\nSnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();\nidGenerator.nextId(); // 170602258864545792\n\nUUIDIdGenerator idGenerator = new UUIDIdGenerator();\nidGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4\n\nSimpleIdGenerator idGenerator = new SimpleIdGenerator();\nidGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05\n注:关于雪花算法、jnanoid 算法的可自行搜索。\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/id.html#id-生成器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/math.html", + "children": [ + { + "title": "数学函数", + "url": "/manual/2.2/core/math.html#数学函数", + "content": "数学函数定义了实用的数学函数。\n\n方法\n说明\n\n\n\n\ncontinuousSum\n计算两个数之间连续相加之和\n\n\nrangeValue\n获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身\n\n\nimport com.buession.core.math.Math;\nlong result = Math.continuousSum(1, 100);\n// 5050\nimport com.buession.core.math.Math;\nlong value = 3;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 4\nimport com.buession.core.math.Math;\nlong value = 5;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 5\nimport com.buession.core.math.Math;\nlong value = 11;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 10\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/math.html#数学函数-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/serializer.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.2/core/serializer.html#构建器", + "content": "构建器对象的序列化和反序列化,包括二进制和 JSON。您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。" + }, + { + "title": "序列化、反序列化类", + "url": "/manual/2.2/core/serializer.html#构建器-序列化、反序列化类", + "content": "序列化、反序列化类\n\n类\n说明\n\n\n\n\nDefaultByteArraySerializer\n将对象序列化为二进制,或将二进制反序列化为对象\n\n\nFastJsonJsonSerializer\n基于 FastJSON 的对象与 JSON 之间的序列化和反序列化\n\n\nGsonJsonSerializer\n基于 Gson 的对象与 JSON 之间的序列化和反序列化\n\n\nJacksonJsonSerializer\n基于 Jackson2 的对象与 JSON 之间的序列化和反序列化\n\n\n通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象\nDefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象\nDefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化\n在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑\nFastJsonJsonSerializer、GsonJsonSerializer、JacksonJsonSerializer 可以通过参数 Class、TypeReference 指定返回的对象类型\ncom.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/serializer.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/validator.html", + "children": [ + { + "title": "验证器", + "url": "/manual/2.2/core/validator.html#验证器", + "content": "验证器数据验证器及其注解。该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。并提供对应的基于 javax.validation 的校验注解。验证是否为 null判断任意对象是否为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNull(obj);\n验证是否不为 null判断任意对象是否不为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNotNull(obj);\n判断字符串是否为空白字符串判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 falseimport com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\n\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\na\";\nboolean result = Validate.isBlank(str); // false\n注:isNotBlank 与之相反\n判断是否为空isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空import com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isEmpty(str); // true\n\nString str = \" \";\nboolean result = Validate.isEmpty(str); // false\n\nboolean result = Validate.isEmpty(new String[]{}); // true\n\nboolean result = Validate.isEmpty(new Integer[]{1}); // false\n注:isNotEmpty 与之相反\n判断是否在两个数之间isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3); // true\n\nboolean result = Validate.isBetween(2, 2, 3); // false\n可通过参数设置是否包含边界值import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3, true); // true\n\nboolean result = Validate.isBetween(2, 2, 3, true); // true\n判断是否为电话号码isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isTel(\"028-12345678\"); // true\n\nboolean result = Validate.isTel(\"028-02345678\"); // false\n判断是否为手机号码isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isMobile(\"028-12345678\"); // false\n\nboolean result = Validate.isMobile(\"13800138000\"); // true\n判断是否为邮政编码isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。import com.buession.core.validator.Validate;\nboolean result = Validate.isPostCode(\"043104\"); // false\n\nboolean result = Validate.isPostCode(\"643104\"); // true\n判断是否为 QQ 号码isQQ 可判断一个字符串是否为 QQ 号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isQQ(\"043104\"); // false\n\nboolean result = Validate.isQQ(\"25132.141\"); // true\n判断是否为身份证号码isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\", true, \"2.10-01-01\");\n其它,更多方法可以参考手册。" + }, + { + "title": "注解", + "url": "/manual/2.2/core/validator.html#验证器-注解", + "content": "注解javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。\n\n注解\n验证的数据类型\n说明\n\n\n\n\n@Alnum\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Alpha\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Numeric\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Between\nshort、int、double 等任何 Number 的子类型\n验证注解的元素值是否为在两个数之间\n\n\n@Empty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否为空\n\n\n@NotEmpty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否不为空\n\n\n@HasText\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@IDCard\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Ip\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Isbn\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@MimeType\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Mobile\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Null\n任意类型\n验证注解的元素值是否为 null\n\n\n@NotNull\n任意类型\n验证注解的元素值是否为 null\n\n\n@Port\nInteger\n验证注解的元素值是否为 null\n\n\n@PostCode\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@QQ\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Tel\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Xdigit\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/validator.html#验证器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/utils.html", + "children": [ + { + "title": "工具类", + "url": "/manual/2.2/core/utils.html#工具类", + "content": "工具类常用通用性工具类。" + }, + { + "title": "Byte 数组比较", + "url": "/manual/2.2/core/utils.html#工具类-byte-数组比较", + "content": "Byte 数组比较ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。" + }, + { + "title": "注解工具", + "url": "/manual/2.2/core/utils.html#工具类-注解工具", + "content": "注解工具AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class clazz, final Class[] annotations)、hasMethodAnnotationPresent(Method method, final Class[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。" + }, + { + "title": "断言", + "url": "/manual/2.2/core/utils.html#工具类-断言", + "content": "断言Assert 和 springframework 中的注解类似,不过逻辑有些相反。" + }, + { + "title": "Byte 工具", + "url": "/manual/2.2/core/utils.html#工具类-byte-工具", + "content": "Byte 工具ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。import com.buession.core.utils.ByteUtils;\nbyte[] bytes;\nchar c = ByteUtils.toChar(bytes);\n\nchar[] chars = ByteUtils.toChar(bytes);\nbyte 数组连接。import com.buession.core.utils.ByteUtils;\nbyte[] dest;\nbyte[] source\nbyte[] result = ByteUtils.concat(dest, source);\n" + }, + { + "title": "Character 工具", + "url": "/manual/2.2/core/utils.html#工具类-character-工具", + "content": "Character 工具CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。import com.buession.core.utils.CharacterUtils;\nchar c;\nbyte[] bytes = ByteUtils.toBytes(c);\n\nchar[] chars;\nbyte[] bytes = ByteUtils.toBytes(chars);\n" + }, + { + "title": "数字工具", + "url": "/manual/2.2/core/utils.html#工具类-数字工具", + "content": "数字工具NumberUtils 提供了对数字相关的操作。\n\n方法\n说明\n\n\n\n\nint2bytes\n将 int 转换为 byte[]\n\n\nbytes2int\n将 byte[] 转换为 int\n\n\nlong2bytes\n将 long 转换为 byte[]\n\n\nbytes2long\n将 byte[] 转换为 long\n\n\nfloat2bytes\n将 float 转换为 byte[]\n\n\nbytes2float\n将 byte[] 转换为 float\n\n\ndouble2bytes\n将 double 转换为 byte[]\n\n\nbytes2double\n将 byte[] 转换为 double\n\n\n" + }, + { + "title": "字符串工具", + "url": "/manual/2.2/core/utils.html#工具类-字符串工具", + "content": "字符串工具StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。截取字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.substr(\"abcde\", 1); // bcde\nString result = StringUtils.substr(\"abcde\", 1, 2); // bc\n生成随机字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.random(length);\n比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 3); // true\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 4); // false\n忽略大小写比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"Abce\", 3); // true\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"aBce\", 4); // false\n" + }, + { + "title": "拼音工具", + "url": "/manual/2.2/core/utils.html#工具类-拼音工具", + "content": "拼音工具PinyinUtils 封装了获取中文拼音、拼音首字母的方法。import com.buession.core.utils.PinyinUtils;\nString result = PinyinUtils.getPinyin(\"中国\"); // zhongguo\nString result = PinyinUtils.getPinYinFirstChar(\"中国\"); // zg\n" + }, + { + "title": "随机数工具", + "url": "/manual/2.2/core/utils.html#工具类-随机数工具", + "content": "随机数工具RandomUtils 封装了随机数的生成。\n\n方法\n说明\n\n\n\n\nnextBoolean\n随机布尔值\n\n\nnextBytes\n随机字节数组\n\n\nnextInt\n生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE\n\n\nnextLong\n生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE\n\n\nnextFloat\n生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE\n\n\nnextDouble\n生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE\n\n\n" + }, + { + "title": "Properties 工具", + "url": "/manual/2.2/core/utils.html#工具类-properties-工具", + "content": "Properties 工具PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。import com.buession.core.utils.SystemPropertyUtils;\nInteger result = PropertiesUtils.getInteger(properties, key);\nBoolean result = PropertiesUtils.getBoolean(properties, key);\n" + }, + { + "title": "System Property 工具", + "url": "/manual/2.2/core/utils.html#工具类-system-property-工具", + "content": "System Property 工具SystemPropertyUtils 封装了系统属性或系统环境变量的操作。设置属性方法 setProperty 对 System.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty。import com.buession.core.utils.SystemPropertyUtils;\nSystemPropertyUtils.setProperty(\"http.port\", 8080);\nSystemPropertyUtils.setProperty(\"http.ssl.enable\", false);\n获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。String value = System.getProperty(name);\nif(Validate.hasText(value) == false){\n value = System.getenv(name);\n}\n" + }, + { + "title": "版本工具", + "url": "/manual/2.2/core/utils.html#工具类-版本工具", + "content": "版本工具VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。import com.buession.core.utils.VersionUtils;\nVersionUtils.compare(\"1.0.0\", \"1.0.1-beta\"); // -1\nVersionUtils.compare(\"1.0.0\", \"1.0.0r\"); // -1\n规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本获取类的版本值import com.buession.core.utils.VersionUtils;\nByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.2.0\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/utils.html#工具类-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/other.html", + "children": [ + { + "title": "其它", + "url": "/manual/2.2/core/other.html#其它", + "content": "其它通用的接口定义,框架自身类,以及其它杂项。" + }, + { + "title": "框架自身工具", + "url": "/manual/2.2/core/other.html#其它-框架自身工具", + "content": "框架自身工具获取 Buession Framework 版本:import com.buession.core.Framework;import com.buession.core.BuesssionFrameworkVersion;\n\nBuesssionFrameworkVersion.getVersion(); // 2.2.0\nFramework.VERSION; // 2.2.0\n获取 Buession Framework 框架名称:import com.buession.core.Framework;\nFramework.NAME; // \"Buession\"\n" + }, + { + "title": "命令执行器", + "url": "/manual/2.2/core/other.html#其它-命令执行器", + "content": "命令执行器命令执行器接口:/** * 命令执行器\n *\n * @param \n * \t\t命令上下文\n * @param \n * \t\t命令执行返回值\n */\n@FunctionalInterface\npublic interface Executor {\n\n\t/**\n\t * 命令执行\n\t *\n\t * @param context\n\t * \t\t命令执行器上下文\n\t *\n\t * @return 命令执行返回值,R 类型的实例\n\t */\n\tR execute(C context);\n\n}\n您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。" + }, + { + "title": "销毁接口", + "url": "/manual/2.2/core/other.html#其它-销毁接口", + "content": "销毁接口功能类似 java.io.Closeable。public interface Destroyable {\n\t/**\n\t * 销毁相关资源\n\t *\n\t * @throws IOException\n\t * \t\tIO 错误时抛出\n\t */\n\tvoid destroy() throws IOException;\n\n}\n" + }, + { + "title": "Rawable", + "url": "/manual/2.2/core/other.html#其它-rawable", + "content": "Rawable原始的,约定实现该接口的类,必须返回原始字节数组。public interface Rawable {\n\t/**\n\t * 返回原始的字节数组\n\t *\n\t * @return 原始的字节数组\n\t */\n\tbyte[] getRaw();\n\n}\n" + }, + { + "title": "名称节点", + "url": "/manual/2.2/core/other.html#其它-名称节点", + "content": "名称节点名称节点,约定实现该接口的类应该返回一个名称public interface NamedNode {\n\t/**\n\t * 返回节点名称\n\t *\n\t * @return 节点名称\n\t */\n\t@Nullable\n\tString getName();\n\n}\n" + }, + { + "title": "分页", + "url": "/manual/2.2/core/other.html#其它-分页", + "content": "分页com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.2/core/exception.html", + "children": [ + { + "title": "异常", + "url": "/manual/2.2/core/exception.html#异常", + "content": "异常通用异常的定义。\n\n异常\n说明\n\n\n\n\nAccessException\n拒绝访问异常\n\n\nClassInstantiationException\n类实例化异常\n\n\nConversionException\n数据类型转换异常\n\n\nDataAlreadyExistException\n数据已存在异常\n\n\nDataNotFoundException\n数据不存在或未找到异常\n\n\nInsteadException\n类方法废弃后,需要使用其它类库方法来替代\n\n\nNestedRuntimeException\n嵌套运行时异常\n\n\nOperationException\n运算异常\n\n\nPresentException\n--\n\n\nSerializationException\n序列化异常\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/core/exception.html#异常-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-cron 参考手册", + "content": "对 quartz 的二次封装", + "url": "/manual/2.2/cron/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/cron/index.html#安装", + "content": "安装 com.buession\n buession-cron\n x.x.x\n\n由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。且在今后的版本中,该模块可能会被废弃。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/cron/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。", + "url": "/manual/2.2/dao/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/dao/index.html#安装", + "content": "安装 com.buession\n buession-dao\n x.x.x\n\n我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。同时,我们在代码层面实现了数据库的读写分离。我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。" + }, + { + "title": "Dao 接口", + "url": "/manual/2.2/dao/index.html#dao-接口", + "content": "Dao 接口接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.2.0/com/buession/dao/Dao.htmlpublic interface Dao {}\nP:主键类型\nE:实体类\n分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset。条件为 Map 类型,允许为 null。排序为 Map 类型,允许为 null。MyBatisBuession Framework 扩展 MyBatis 的文档。MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/dao/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.2/dao/mybatis.html", + "children": [ + { + "title": "MyBatis", + "url": "/manual/2.2/dao/mybatis.html#mybatis", + "content": "MyBatisBuession Framework 扩展 MyBatis 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.2/dao/mybatis.html#mybatis-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplate、slaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。" + }, + { + "title": "Mybatis 约定", + "url": "/manual/2.2/dao/mybatis.html#mybatis-mybatis-约定", + "content": "Mybatis 约定如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace\nnamespace com.buession.dao.test.dao;\npublic class UserDaoImpl extends AbstractMyBatisDao {\n\n\t@Override\n\tprotected String getStatement(){\n\t\treturn \"com.buession.dao.test.dao.UserMapper\";\n\t}\n\n}\n\n\nMapper 的 SQL ID 和方法名保持一致\n\n\nSQL ID\n说明\n返回值\n\n\n\n\ninsert\n插入数据\n影响的行数\n\n\nbatchInsert\n批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入\n每次插入影响的行数列表\n\n\nreplace\n替换数据,即:REPLACE 语句\n影响的行数\n\n\nbatchReplace\n批量替换数据,即:REPLACE 语句\n每次替换数据影响的行数列表\n\n\nupdate\n更新数据\n更新条数\n\n\nupdateByPrimary\n根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值\n更新条数\n\n\ngetByPrimary\n根据主键查询数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselectOne\n(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselect\n查询数据\n数据结果列表\n\n\ngetAll\n查询所有数据\n数据结果列表\n\n\ncount\n获取记录数\n记录数\n\n\ndeleteByPrimary\n根据主键删除数据\n影响条数\n\n\ndelete\n删除数据\n影响条数\n\n\nclear\n清除数据\n影响条数\n\n\ntruncate\n截断数据\n影响条数\n\n\n注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。\n以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。\n" + }, + { + "title": "Mybatis 类型处理器", + "url": "/manual/2.2/dao/mybatis.html#mybatis-mybatis-类型处理器", + "content": "Mybatis 类型处理器MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao。\n\nTypeHandler\n说明\n\n\n\n\nDefaultEnumTypeHandler\n默认 Enum 类型处理器,将值直接转换为枚举字段\n\n\nIgnoreCaseEnumTypeHandler\n忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段\n\n\nDefaultJsonTypeHandler\nJSON 处理器,将 JSON 格式的字符串值和类型 进行转换\n\n\nDefaultSetEnumTypeHandler\n默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素\n\n\nIgnoreCaseSetEnumTypeHandler\n忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素\n\n\nDefaultSetTypeHandler\n默认 Set 类型处理器,将值以 \",\" 拆分转换为 Set\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/dao/mybatis.html#mybatis-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.2/dao/mongodb.html", + "children": [ + { + "title": "MongoDB", + "url": "/manual/2.2/dao/mongodb.html#mongodb", + "content": "MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.2/dao/mongodb.html#mongodb-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplate、slaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。AbstractMongoDBDao 的 replace 执行的也是 insert。在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/dao/mongodb.html#mongodb-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-geoip 参考手册", + "content": "对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。", + "url": "/manual/2.2/geoip/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/geoip/index.html#安装", + "content": "安装 com.buession\n buession-geoip\n x.x.x\n\n通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。" + }, + { + "title": "获取国家信息", + "url": "/manual/2.2/geoip/index.html#获取国家信息", + "content": "获取国家信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nCountry country = resolver.country(\"114.114.114.114\");\n// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}\n\nCountry country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8\n// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}\n" + }, + { + "title": "获取城市信息", + "url": "/manual/2.2/geoip/index.html#获取城市信息", + "content": "获取城市信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nDistrict district = resolver.district(\"114.114.114.114\");\n// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}\n\nDistrict district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8\n// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}\n" + }, + { + "title": "获取位置信息", + "url": "/manual/2.2/geoip/index.html#获取位置信息", + "content": "获取位置信息位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nLocation location = resolver.location(\"114.114.114.114\");\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Shanghai\",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}\n\nLocation location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Seoul\",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}\n" + }, + { + "title": "缓存", + "url": "/manual/2.2/geoip/index.html#缓存", + "content": "缓存为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。" + }, + { + "title": "Resolver 的 Spring Factory Bean", + "url": "/manual/2.2/geoip/index.html#resolver-的-spring-factory-bean", + "content": "Resolver 的 Spring Factory Bean我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。dbPath 和 stream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。\nenableCache 可以控制是否缓存。\n" + }, + { + "title": "关于 IP 库", + "url": "/manual/2.2/geoip/index.html#关于-ip-库", + "content": "关于 IP 库buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/geoip/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-git 参考手册", + "content": "获取项目 GIT 信息。", + "url": "/manual/2.2/git/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/git/index.html#安装", + "content": "安装 com.buession\n buession-git\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/git/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。", + "url": "/manual/2.2/httpclient/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/httpclient/index.html#安装", + "content": "安装 com.buession\n buession-httpclient\n x.x.x\n\n我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponents 和 okhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。传统的方式: org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\nimport org.apache.http.HttpResponse;import org.apache.http.conn.HttpClientConnectionManager;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.client.methods.HttpPost;\n\nHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();\n\nHttpResponse response = httpClient.execute(new HttpPost(\"https://www.buession.com/\"));\n或者 com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport okhttp3.HttpClientConnectionManager;import okhttp3.OkHttpClient;\nimport okhttp3.ConnectionPool;\nimport okhttp3.Request;\nimport okhttp3.Request.Builder;\nimport okhttp3.Response;\n\nOkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());\nHttpClient httpClient = builder.build();\n\nBuilder requestBuilder = new Builder().post();\nrequestBuilder.url(\"https://www.buession.com/\");\nRequest okHttpRequest = requestBuilder.build();\n\nResponse httpResponse = httpClient.newCall(okHttpRequest).execute();\n现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。 com.buession\n buession-httpclient\n x.x.x\n\n\n org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\n或者 com.buession\n buession-httpclient\n x.x.x\n\n\n com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.OkHttpHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.conn.OkHttpClientConnectionManager;\nimport com.buession.httpclient.core.Response;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\n" + }, + { + "title": "展望", + "url": "/manual/2.2/httpclient/index.html#展望", + "content": "展望目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.2) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/httpclient/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.2/httpclient/configuration.html", + "children": [ + { + "title": "连接配置", + "url": "/manual/2.2/httpclient/configuration.html#连接配置", + "content": "连接配置您可以通过连接配置类 Configuration 配置 apache httpcomponents 和 okhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponents 或 okhttp3 的配置信息。" + }, + { + "title": "配置属性说明", + "url": "/manual/2.2/httpclient/configuration.html#连接配置-配置属性说明", + "content": "配置属性说明\n\n属性名称\n数据类型\napache httpcomponents 对应配置\nokhttp3 对应配置\n默认值\n说明\n\n\n\n\nmaxConnections\nint\nmaxTotal\nmaxIdleConnections\n5000\n最大连接数\n\n\nmaxPerRoute\nint\ndefaultMaxPerRoute\n--\n500\n每个路由的最大连接数\n\n\nidleConnectionTime\nint\ncloseIdleConnections\nkeepAliveDuration\n60000\n空闲连接存活时长(单位:毫秒)\n\n\nconnectTimeout\nint\nconnectTimeout\nconnectTimeout\n3000\n连接超时时间(单位:毫秒)\n\n\nconnectionRequestTimeout\nint\nconnectionRequestTimeout\n--\n5000\n从连接池获取连接的超时时间(单位:毫秒)\n\n\nreadTimeout\nint\nsocketTimeout\nreadTimeout\n5000\n读取超时时间(单位:毫秒)\n\n\nallowRedirects\nBoolean\nredirectsEnabled\nfollowRedirects\n--\n是否允许重定向\n\n\nrelativeRedirectsAllowed\nBoolean\nrelativeRedirectsAllowed\n--\n--\n是否应拒绝相对重定向\n\n\ncircularRedirectsAllowed\nBoolean\ncircularRedirectsAllowed\n--\n--\n是否允许循环重定向\n\n\nmaxRedirects\nInteger\nmaxRedirects\n--\n--\n最大允许重定向次数\n\n\nauthenticationEnabled\nboolean\nauthenticationEnabled\n--\n--\n是否开启 Http Basic 认证\n\n\ncontentCompressionEnabled\nboolean\ncontentCompressionEnabled\n--\n--\n是否启用内容压缩\n\n\nnormalizeUri\nboolean\nnormalizeUri\n--\n--\n是否标准化 URI\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/httpclient/configuration.html#连接配置-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.2/httpclient/connectionmanager.html", + "children": [ + { + "title": "连接管理器", + "url": "/manual/2.2/httpclient/connectionmanager.html#连接管理器", + "content": "连接管理器连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponents 和 okhttp3 的文档。您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponents 或 okhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。" + }, + { + "title": "关于 okhttp 连接管理器", + "url": "/manual/2.2/httpclient/connectionmanager.html#连接管理器-关于-okhttp-连接管理器", + "content": "关于 okhttp 连接管理器okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/httpclient/connectionmanager.html#连接管理器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.2/httpclient/response.html", + "children": [ + { + "title": "响应", + "url": "/manual/2.2/httpclient/response.html#响应", + "content": "响应当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。buession-httpclient 会将 apache httpcomponents 或 okhttp3 的响应对象,转换为 Response。需要注意的是,原生 apache httpcomponents 或 okhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。import com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.core.Response;\nimport java.io.InputStream;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\nInputStream stream = response.getInputStream(); // 以流的形式获取响应体\nString body = response.getBody(); // 以字符串的形式获取响应体\n\nstream.close();\ngetInputStream、getBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponents 或 okhttp3 返回的流。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/httpclient/response.html#响应-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.2/httpclient/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.2/httpclient/method.html#方法", + "content": "方法buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。示例:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\");\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\");\n您可以自定义请求头:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.List;\nimport java.util.ArrayList;\n\nList headers = new ArrayList();\n\nheaders.add(new Header(\"X-SDK-NAME\", \"Buession\"));\nheaders.add(new Header(\"X-Timestamp\", System.currentTimeMillis()));\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", headers);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", headers);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", headers);\n您可以设置请求参数:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.Map;\nimport java.util.HashMap;\n\nMap parameters = new HashMap();\n\nparameters.put(\"action\", \"edit\");\nparameters.put(\"id\", 1);\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", parameters);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", parameters);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", parameters);\n您可以设置请求体:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport jcom.buession.httpclient.core.RequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\n\nEncodedFormRequestBody requestBody = new EncodedFormRequestBody();\n\nrequestBody.addRequestBodyElement(\"username\", \"buession\");\nrequestBody.addRequestBodyElement(\"password\", \"buession\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", requestBody);\n\nJsonRawRequestBody requestBody = new JsonRawRequestBody(new User());\n// PUT 请求\nResponse response = httpClient.put(\"https://www.buession.com/\", requestBody);\n不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody。" + }, + { + "title": "RequestBody", + "url": "/manual/2.2/httpclient/method.html#方法-requestbody", + "content": "RequestBody\n\nRequestBody\nContent-Type\n说明\n\n\n\n\nInputStreamRequestBody\napplication/octet-stream\n二进制请求体\n\n\nChunkedInputStreamRequestBody\napplication/octet-stream\nChunked 二进制请求体\n\n\nRepeatableInputStreamRequestBody\napplication/octet-stream\nRepeatable 二进制请求体\n\n\nEncodedFormRequestBody\napplication/x-www-form-urlencoded\n普通表单请求体\n\n\nMultipartFormRequestBody\nmultipart/form-data\n文件上传表单请求体\n\n\nHtmlRawRequestBody\ntext/html\nHTML 请求体\n\n\nJavaScriptRawRequestBody\napplication/javascript\nJavaScript 请求体\n\n\nJsonRawRequestBody\napplication/json\nJSON 请求体\n\n\nTextRawRequestBody\ntext/plain\nTEXT 请求体\n\n\nXmlRawRequestBody\ntext/xml\nXML 请求体\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/httpclient/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-io 参考手册", + "content": "封装了对文件的操作", + "url": "/manual/2.2/io/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/io/index.html#安装", + "content": "安装 com.buession\n buession-io\n x.x.x\n\n该模块二次封装了 java java.io.File 和 java.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。" + }, + { + "title": "读取文件", + "url": "/manual/2.2/io/index.html#读取文件", + "content": "读取文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nbyte[] result = file.read();\n" + }, + { + "title": "写文件", + "url": "/manual/2.2/io/index.html#写文件", + "content": "写文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nfile.write(\"Buession\");\nfile.write(\"Buession\".getBytes());\nfile.write(\"Buession\", true); // 追加写\n" + }, + { + "title": "获取文件 MD5、SHA-1值", + "url": "/manual/2.2/io/index.html#获取文件-md5、sha-1值", + "content": "获取文件 MD5、SHA-1值import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nString md5 = file.getMd5(); // 获取文件 MD5\nString sha1 = file.getSha1(); // 获取文件 SHA-1\n" + }, + { + "title": "获取文件 MD5、SHA-1 值", + "url": "/manual/2.2/io/index.html#获取文件-md5、sha-1-值", + "content": "获取文件 MD5、SHA-1 值import com.buession.io.file.File;import com.buession.io.MimeType;\n\nFile file = new File(\"/tmp/debug.txt\");\n\nMimeType result = file.getMimeType();\n" + }, + { + "title": "设置文件权限", + "url": "/manual/2.2/io/index.html#设置文件权限", + "content": "设置文件权限import com.buession.io.file.Files;\nFiles.chmod(\"/tmp/debug.txt\", 0777);\n" + }, + { + "title": "设置文件用户组", + "url": "/manual/2.2/io/index.html#设置文件用户组", + "content": "设置文件用户组import com.buession.io.file.Files;\nFiles.chgrp(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "设置文件用户", + "url": "/manual/2.2/io/index.html#设置文件用户", + "content": "设置文件用户import com.buession.io.file.Files;\nFiles.chown(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "注解", + "url": "/manual/2.2/io/index.html#注解", + "content": "注解注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。import com.buession.io.json.annotation.MimeTypeString;\nclass File {\n\n @MimeTypeString\n private MimeType mime;\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/io/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-jdbc 参考手册", + "content": "JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。", + "url": "/manual/2.2/jdbc/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/jdbc/index.html#安装", + "content": "安装 com.buession\n buession-jdbc\n x.x.x\n\n通过提供的 API,您可以简化对 DBCP2、Druid、Hikari、Tomcat 数据源的初始化,该类库基本不单独使用。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/jdbc/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-json 参考手册", + "content": "主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。", + "url": "/manual/2.2/json/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/json/index.html#安装", + "content": "安装 com.buession\n buession-json\n x.x.x\n\n封装了大量基于 jackson 的注解。" + }, + { + "title": "注解", + "url": "/manual/2.2/json/index.html#注解", + "content": "注解\n\n注解\n说明\n\n\n\n\nCalendarUnixTimestamp\njava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar\n\n\nDateUnixTimestamp\njava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date\n\n\nSqlDateUnixTimestamp\njava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date\n\n\nTimestampUnixTimestamp\njava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp\n\n\nJsonEnum2Map\n枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举\n\n\nSensitive\n通过该注解可以实现数据的脱敏\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/json/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-lang 参考手册", + "content": "常用 POJO 类和枚举的定义,详细查看 API 参考手册。", + "url": "/manual/2.2/lang/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/lang/index.html#安装", + "content": "安装 com.buession\n buession-lang\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/lang/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-net 参考手册", + "content": "网络相关工具类。", + "url": "/manual/2.2/net/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/net/index.html#安装", + "content": "安装 com.buession\n buession-net\n x.x.x\n\n" + }, + { + "title": "IP 地址工具类", + "url": "/manual/2.2/net/index.html#ip-地址工具类", + "content": "IP 地址工具类IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。import com.buession.net.utils.InetAddressUtis;\nlong result = InetAddressUtis.ip2long(\"127.0.0.1\"); // 2130706433\nString ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1\nURI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/net/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。", + "url": "/manual/2.2/redis/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/redis/index.html#安装", + "content": "安装 com.buession\n buession-redis\n x.x.x\n\n" + }, + { + "title": "介绍", + "url": "/manual/2.2/redis/index.html#介绍", + "content": "介绍buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。import com.buession.redis.RedisTemplate;import com.buession.redis.core.Options;\nimport com.buession.core.serializer.type.TypeReference;\nimport java.utils.Map;\nimport java.utils.HashMap;\n\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.setOptions(new Options());\nredisTemplate.afterPropertiesSet();\n\n// 将 User 对象写进 key 为 user hash 中\nredisTemplate.hSet(\"user\", \"1\", new User());\n\n// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User\nUser user = redisTemplate.hGetObject(\"user\", \"1\", User.class);\n\n// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User\nMap data = redisTemplate.hGetAllObject(\"user\", \"1\", new TypeReference>{});\n" + }, + { + "title": "展望", + "url": "/manual/2.2/redis/index.html#展望", + "content": "展望目前,buession-redis 仅支持 jedis,不支持 lettuce,我们计划在 2.3 ~ 2.5 的版本中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/redis/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.2/redis/datasource.html", + "children": [ + { + "title": "数据源", + "url": "/manual/2.2/redis/datasource.html#数据源", + "content": "数据源buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。数据源 DataSource 包括三个子接口:StandaloneDataSource:单机模式数据源\nSentinelDataSource:哨兵模式数据源\nClusterDataSource:集群模式数据源\njedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。测试环境 properties:redis.host=127.0.0.1redis.port=6379\nredis.password=\n生产环境 properties:redis.host=192.168.100.131redis.port=6379\nredis.password=passwd\n" + }, + { + "title": "连接池", + "url": "/manual/2.2/redis/datasource.html#数据源-连接池", + "content": "连接池通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfig 和 ConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。连接池配置\n\n配置项\n数据类型\n-- 默认值\n说明\n\n\n\n\nlifo\nboolean\nGenericObjectPoolConfig.DEFAULT_LIFO\n池模式,为 true 时,后进先出;为 false 时,先进先出\n\n\nfairness\nboolean\nGenericObjectPoolConfig.DEFAULT_FAIRNESS\n当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制\n\n\nmaxWait\nDuration\nGenericObjectPoolConfig.DEFAULT_MAX_WAIT\n当连接池资源用尽后,调用者获取连接时的最大等待时间\n\n\nminEvictableIdleTime\nDuration\n60000\n连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除\n\n\nsoftMinEvictableIdleTime\nDuration\nGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION\n连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数\n\n\nevictionPolicyClassName\nString\nGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME\n驱逐策略的类名\n\n\nevictorShutdownTimeout\nDuration\nGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT\n关闭驱逐线程的超时时间\n\n\nnumTestsPerEvictionRun\nint\n-1\n检测空闲对象线程每次运行时检测的空闲对象的数量\n\n\ntestOnCreate\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE\n在创建对象时检测对象是否有效,配置 true 会降低性能\n\n\ntestOnBorrow\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW\n在从对象池获取对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestOnReturn\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN\n在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestWhileIdle\nboolean\ntrue\n在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\nblockWhenExhausted\nboolean\nGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED\n当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\njmxEnabled\nboolean\nGenericObjectPoolConfig.DEFAULT_JMX_ENABLE\n是否注册 JMX\n\n\njmxNamePrefix\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX\nJMX 前缀\n\n\njmxNameBase\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE\n使用 base + jmxNamePrefix + i 来生成 ObjectName\n\n\nmaxTotal\nint\nGenericObjectPoolConfig.DEFAULT_MAX_TOTAL\n最大连接数\n\n\nminIdle\nint\nGenericObjectPoolConfig.DEFAULT_MIN_IDLE\n最小空闲连接数\n\n\nmaxIdle\nint\nGenericObjectPoolConfig.DEFAULT_MAX_IDLE\n最大空闲连接数\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/redis/datasource.html#数据源-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.2/redis/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.2/redis/method.html#方法", + "content": "方法buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。import com.buession.redis.BaseRedisTemplate;\nBaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 删除哈希表 key 中的一个或多个指定域\nredisTemplate.hDel(\"user\", \"1\", \"2\", \"3\");\n\n// 检查给定 key 是否存在\nredisTemplate.exists(\"user\");\n\n// 获取列表 key 中,下标为 index 的元素\nredisTemplate.lIndex(\"user\", 1);\n\n// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾\nredisTemplate.append(\"key\", \"value 1\");\nBaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。import com.buession.redis.RedisTemplate;\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类\nUser user = redisTemplate.lIndexObject(\"user\", 1, User.class);\n序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/redis/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-session 参考手册", + "content": "无文档", + "url": "/manual/2.2/session/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/session/index.html#安装", + "content": "安装 com.buession\n buession-session\n x.x.x\n\n该模块无实际意义,将在今后的版本中会删除掉。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/session/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-thesaurus 参考手册", + "content": "对词库的解析,目前仅支持搜狗词条。", + "url": "/manual/2.2/thesaurus/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/thesaurus/index.html#安装", + "content": "安装 com.buession\n buession-thesaurus\n x.x.x\n\n您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。import com.buession.thesaurus.SogouParser;import com.buession.thesaurus.Parser;\nimport com.buession.thesaurus.core.Word;\nimport java.util.Set;\n\nParser parser = new SogouParser();\n\nSet words parser.parse(\"搜谱拼音词条文件路径\");\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/thesaurus/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-velocity 参考手册", + "content": "spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。", + "url": "/manual/2.2/velocity/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/velocity/index.html#安装", + "content": "安装 com.buession\n buession-velocity\n x.x.x\n\n该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/velocity/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。", + "url": "/manual/2.2/web/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.2/web/index.html#安装", + "content": "安装 com.buession\n buession-web\n x.x.x\n\nbuession-web 扩展了 spring-webmvc、spring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.2/web/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.2/web/annotation.html", + "children": [ + { + "title": "注解", + "url": "/manual/2.2/web/annotation.html#注解", + "content": "注解我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。" + }, + { + "title": "注解", + "url": "/manual/2.2/web/annotation.html#注解-注解", + "content": "注解\n\n注解\nRequest / Response\n作用域\n说明\n\n\n\n\n@RequestClientIp\nrequest\n方法参数\n获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress\n\n\n@ContentType\nresponse\n类、方法\n设置响应 Content-Type\n\n\n@HttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值\n\n\n@DisableHttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存\n\n\n@ResponseHeader\nresponse\n类、方法\n设置响应头\n\n\n@ResponseHeaders\nresponse\n类、方法\n批量设置响应头\n\n\n@DocumentMetaData\nresponse\n类、方法\n设置页面标题、页面编码、关键字、描述、版权等等元信息\n\n\n获取用户端真实 IP@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n设置页面缓存@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n以上,会自动计算 Cache-Control 和 pragma 的值。当然,您也可以手动指定。@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\", cacheControl=\"public, max-age=5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.2/web/filter.html", + "children": [ + { + "title": "过滤器", + "url": "/manual/2.2/web/filter.html#过滤器", + "content": "过滤器我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。" + }, + { + "title": "过滤器", + "url": "/manual/2.2/web/filter.html#过滤器-过滤器", + "content": "过滤器\n\n过滤器\n说明\n\n\n\n\nPoweredByFilter\nPowered By 过滤器\n\n\nPrintUrlFilter\n打印当前请求 URL 过滤器\n\n\nResponseHeaderFilter\n响应头过滤器,设置响应头\n\n\nResponseHeadersFilter\n响应头过滤器,批量设置响应头\n\n\nServerInfoFilter\nServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点\n\n\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.2/web/restful.html", + "children": [ + { + "title": "RESTFUL", + "url": "/manual/2.2/web/restful.html#restful", + "content": "RESTFULRestful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。@RestController@RequestMapping(path = \"/example\")\npublic class ExampleController extends AbstractRestController {\n\n\t@Override\n\tpublic Response add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){\n\t\t\n\t}\n\n\t@Override\n\tpublic Response edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id, @RequestBody ExampleDto example){\n\n\t}\n\n\t@Override\n\tpublic Response detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\n\t}\n\n\t@Override\n\tpublic Response delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.2/web/utils.html", + "children": [ + { + "title": "工具", + "url": "/manual/2.2/web/utils.html#工具", + "content": "工具我们封装了一些 web 相关的工具类,用于处理 request、response。servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。获取客户端真实 IP 地址:RequestUtils.getClientIp(request);我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr是否是 Ajax 请求:RequestUtils.isAjaxRequest(request);是否是移动设备请求:RequestUtils.isMobile(request);设置缓存:ResponseUtils.httpCache(response, 5); // 缓存 5 秒ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点\n" + } + ] + }, + { + "title": "API 参考手册", + "content": "Buession Framework API 包含以下目录:\n\n模块\n使用帮助\n手册\n\n\n\n\nbuession-aop\n使用帮助\nAPI 手册\n\n\nbuession-beans\n使用帮助\nAPI 手册\n\n\nbuession-core\n使用帮助\nAPI 手册\n\n\nbuession-cron\n使用帮助\nAPI 手册\n\n\nbuession-dao\n使用帮助\nAPI 手册\n\n\nbuession-geoip\n使用帮助\nAPI 手册\n\n\nbuession-git\n使用帮助\nAPI 手册\n\n\nbuession-httpclient\n使用帮助\nAPI 手册\n\n\nbuession-io\n使用帮助\nAPI 手册\n\n\nbuession-jdbc\n使用帮助\nAPI 手册\n\n\nbuession-json\n使用帮助\nAPI 手册\n\n\nbuession-lang\n使用帮助\nAPI 手册\n\n\nbuession-net\n使用帮助\nAPI 手册\n\n\nbuession-redis\n使用帮助\nAPI 手册\n\n\nbuession-session\n使用帮助\nAPI 手册\n\n\nbuession-thesaurus\n使用帮助\nAPI 手册\n\n\nbuession-velocity\n使用帮助\nAPI 手册\n\n\nbuession-web\n使用帮助\nAPI 手册\n\n\n", + "url": "/manual/2.1/index.html", + "children": [] + }, + { + "title": "buession-aop 参考手册", + "content": "AOP 封装,方便实现自定义注解", + "url": "/manual/2.1/aop/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/aop/index.html#安装", + "content": "安装 com.buession\n buession-aop\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/aop/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-beans 参考手册", + "content": "该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。", + "url": "/manual/2.1/beans/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/beans/index.html#安装", + "content": "安装 com.buession\n buession-beans\n x.x.x\n\n" + }, + { + "title": "属性拷贝", + "url": "/manual/2.1/beans/index.html#属性拷贝", + "content": "属性拷贝使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。import com.buession.beans.BeanUtils;\nBeanUtils.copyProperties(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.copyProperties(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "属性映射", + "url": "/manual/2.1/beans/index.html#属性映射", + "content": "属性映射使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。import com.buession.beans.BeanUtils;\nBeanUtils.populate(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.populate(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "Bean 转换为 Map", + "url": "/manual/2.1/beans/index.html#bean-转换为-map", + "content": "Bean 转换为 Map使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Keyimport com.buession.beans.BeanUtils;\nMap result = BeanUtils.toMap(bean)\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/beans/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。", + "url": "/manual/2.1/core/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/core/index.html#安装", + "content": "安装 com.buession\n buession-core\n x.x.x\n\n构建器Map、集合的便捷式构建,减少您的代码行数编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中收集器数组、Map、集合的工具类上下文定义应用上下文的类库、注解转换器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。日期时间日期、时间工具ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。数学函数定义了实用的数学函数序列化和反序列化对象的序列化和反序列化,包括二进制和 JSON。验证器数据验证器及其注解工具类常用通用性工具类其它通用的接口定义,框架自身类异常通用异常的定义" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/builder.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.1/core/builder.html#构建器", + "content": "构建器Map、集合的便捷式构建,减少您的代码行数。您需要往 Map、List 中添加元素的传统写法是:import java.util.ArrayList;import java.util.List;\nimport java.util.HashMap;\nimport java.util.Map;\n\nList list = new ArrayList();\nlist.add(\"A\");\nlist.add(\"B\");\nlist.add(\"C\");\n\nMap map = new HashMap();\nmap.put(\"a\", \"A\");\nmap.put(\"b\", \"B\");\nmap.put(\"c\", \"C\");\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.Map;\n\nList list = ListBuilder.create().add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create().put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.LinkedHashMap;\n\nList list = ListBuilder.create(LinkedList.class).add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create(LinkedHashMap.class).put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数\n当您有 value 为 null 时,不添加到 List 时,传统写法:import java.util.ArrayList;import java.util.List;\n\nString value = null;\nList list = new ArrayList();\n\nif(value != null){\n\tlist.add(value);\n}\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import java.util.List;\n\nString value = null;\nList list = ListBuilder.create().addIfPresent(value).build();\nMap、Set、Queue 同理。" + }, + { + "title": "便捷方法", + "url": "/manual/2.1/core/builder.html#构建器-便捷方法", + "content": "便捷方法\n\n方法\n说明\n\n\n\n\n List ListBuilder.epmty()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of(V value)\n创建仅有一个元素的 V 类型的 List 对象\n\n\n Queue QueueBuilder.epmty()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Queue 对象\n\n\n Set SetBuilder.epmty()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Set 对象\n\n\n Map MapBuilder.epmty()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of(V value)\n创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\nempty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/builder.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/codec.html", + "children": [ + { + "title": "编码器", + "url": "/manual/2.1/core/codec.html#编码器", + "content": "编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。USER_NOT_FOUND.code = 10404USER_NOT_FOUND.message = 用户不存在\n\nUSER_LOGIN_FAILURE.code = 10405\nUSER_LOGIN_FAILURE.message = 登录失败\n\n\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(\"USER_NOT_FOUND\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。USER_NOT_FOUND.errorCode = 10404USER_NOT_FOUND.errorMessage = 用户不存在\n\nUSER_LOGIN_FAILURE.errorCode = 10405\nUSER_LOGIN_FAILURE.errorMessage = 登录失败\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(value = \"USER_NOT_FOUND\", code = \"errorCode\", message = \"errorMessage\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/codec.html#编码器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/collect.html", + "children": [ + { + "title": "收集器", + "url": "/manual/2.1/core/collect.html#收集器", + "content": "收集器数组、Map、集合的工具类" + }, + { + "title": "数组", + "url": "/manual/2.1/core/collect.html#收集器-数组", + "content": "数组数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 List、Set 以及字符串类型的数组、数组合并、数组元素操作等方法。检测数组 array 中是否存在元素 element:import com.buession.core.collect.Arrays;\nboolean result = Arrays.contains(array, element);\n返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.indexOf(array, element);\n返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.lastIndexOf(array, element);\n将字符串拼接会字符串:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString result = Arrays.toString(array);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString glue = \"-\";\nString result = Arrays.toString(array, glue);\n// 1-2-3\n可以通过方法 toList、toSet 转换为 List 和 Set:import com.buession.core.collect.Arrays;import java.util.List;\nimport java.util.Set;\n\nint[] array = {1, 2, 3};\nList list = Arrays.toList(array);\nSet set = Arrays.toSet(array);\n将数组转换为字符串类型的数组:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString[] result = Arrays.toStringArray(array);\n将数组进行合并:import com.buession.core.collect.Arrays;\nString[] result = Arrays.toStringArray(array1, array2, array3);\n对数组元素进行操作:import com.buession.core.collect.Arrays;\nString[] array = {\"A\", \"B\", \"C\"};\nString[] result = Arrays.map(array, String.class, fn);\n第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现" + }, + { + "title": "Lists", + "url": "/manual/2.1/core/collect.html#收集器-lists", + "content": "ListsList 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。将字符串拼接会字符串:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString result = Lists.toString(list);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Lists.toString(list);\n// 1-2-3\n可以通过方法 toSet 将 List 转换为 Set:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nSet set = Lists.toSet(list);\n" + }, + { + "title": "Sets", + "url": "/manual/2.1/core/collect.html#收集器-sets", + "content": "SetsSett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。将字符串拼接会字符串:import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString result = Sets.toString(set);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Sets.toString(list);\n// 1-2-3\n可以通过方法 toList 将 Set 转换为 List:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nList list = Sets.toList(set);\n" + }, + { + "title": "Maps", + "url": "/manual/2.1/core/collect.html#收集器-maps", + "content": "MapsMap 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。对 Map 进行操作:import com.buession.core.collect.Maps;import java.util.Map;\nimport java.util.HashMap;\n\nMap maps = new HashMap();\nMap result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());\n第二个、第三参数为 java.util.function.Function 的实现可以通过方法 toList 将 Map 的 value 转换为 List:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\n\nList list = Maps.toList(maps);\n可以通过方法 toSet 将 Map 的 value 转换为 Set:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.Set;\n\nSet set = Maps.toSet(maps);\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/collect.html#收集器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/context.html", + "children": [ + { + "title": "上下文", + "url": "/manual/2.1/core/context.html#上下文", + "content": "上下文注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:逻辑少\n与 Dao 层进行交互,多个 Dao 层的复用\nService 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层\nimport com.buession.core.context.stereotype.Manager;import org.springframework.stereotype.Service;\n\npublic interface UserManager {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Manager\npublic class UserManagerImpl implements UserManager {\n\n\t@Autowired\n\tprivate UserDao userDao;\n\n\t@Autowired\n\tprivate UserProfileDao userProfileDao;\n\n\t@Autowired\n\tprivate RedisTemplate redisTemplate;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = redisTemplate.hGetObject(\"user\", Integer.toString(id), User.class);\n\n\t\tif(user == null){\n\t\t\tuser = userDao.getByPrimary(id);\n\t\t\tif(user != null){\n\t\t\t\tuser.setProfile(userProfileDao.getByUserId(id));\n\t\t\t\tredisTemplate.hSet(\"user\", Integer.toString(id), user);\n\t\t\t}else{\n\t\t\t\tthrow new RuntimeException(\"用户不存在\");\n\t\t\t}\n\t\t}\n\n\t\treturn user;\n\t}\n\n}\n\npublic interface UserService {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Service\npublic class UserServiceImpl implements UserService {\n\n\t@Autowired\n\tprivate UserManager userManager;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = userManager.getByPrimary(id);\n\n\t\t...\n\n\t\treturn user;\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/context.html#上下文-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/converter.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.1/core/converter.html#构建器", + "content": "构建器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。接口定义:@FunctionalInterfacepublic interface Converter {\n\n\tT convert(final S source);\n\n}\n将类似为 S 的对象转换为类型为 T 的对象。" + }, + { + "title": "内置转换器", + "url": "/manual/2.1/core/converter.html#构建器-内置转换器", + "content": "内置转换器\n\n转换器\n说明\n\n\n\n\nArrayConverter\n将 S 类型的数组转换为 T 类型的数组\n\n\nEnumConverter\n枚举转换器,将字符串转换为枚举 E\n\n\nBinaryEnumConverter\n枚举转换器,将 byte 数组转换为枚举 E\n\n\nBooleanStatusConverter\n将布尔值转换为 com.buession.lang.Status\n\n\nStatusBooleanConverter\n将 com.buession.lang.Status 转换为布尔值\n\n\nPredicateStatusConverter\n通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status\n\n\nListConverter\n将 S 类型的 List 对象转换为 T 类型的 List 对象\n\n\nSetConverter\n将 S 类型的 Set 对象转换为 T 类型的 Set 对象\n\n\nMapConverter\n将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map\n\n\n将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象import com.buession.core.converter.MapConverter;import java.util.Map;\n\nMap source;\nMap target;\nMapConverter converter = new MapConverter();\n\ntarget = converter.convert(source);\n将字符串转换为枚举import com.buession.core.converter.EnumConverter;import com.buession.lang.Gender;\n\nGender target;\nEnumConverter converter = new EnumConverter(Gender.class);\n\ntarget = converter.convert(\"FEMALE\");\n// Gender.FEMALE\n\ntarget = converter.convert(\"F\");\n// null\n" + }, + { + "title": "POJO 类映射", + "url": "/manual/2.1/core/converter.html#构建器-pojo-类映射", + "content": "POJO 类映射我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。public interface Mapper {\n\t/**\n\t * 将源对象映射到目标对象\n\t *\n\t * @param object\n\t * \t\t源对象\n\t *\n\t * @return 目标对象实例\n\t */\n\tT mapping(S object);\n\n\t/**\n\t * 将源对象数组映射到目标对象数组\n\t *\n\t * @param object\n\t * \t\t源对象数组\n\t *\n\t * @return 目标对象实例数组\n\t */\n\tT[] mapping(S[] object);\n\n\t/**\n\t * 将源 list 对象映射到目标 list 对象\n\t *\n\t * @param object\n\t * \t\t源 list 对象\n\t *\n\t * @return 目标对象 list 实例\n\t */\n\tList mapping(List object);\n\n\t/**\n\t * 将源 set 对象映射到目标 set 对象\n\t *\n\t * @param object\n\t * \t\t源 set 对象\n\t *\n\t * @return 目标对象 set 实例\n\t */\n\tSet mapping(Set object);\n\n}\n我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样;并在此基础上增加了方法 alwaysApplyingWhenHasText(),用于判断映射源是否为 null 或者是否含有字符串。import com.buession.core.converter.mapper.PropertyMapper;\nUser source = new User();\nUser target = new User();\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();\npropertyMapper.form(source::getId).to(target:setId)\n// null\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenHasText();\npropertyMapper.form(source::getUsername).to(target:setUsername)\n// null\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/converter.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/datetime.html", + "children": [ + { + "title": "日期时间", + "url": "/manual/2.1/core/datetime.html#日期时间", + "content": "日期时间日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。获取当前 Unix 时间戳(秒):import com.buession.core.datetime.DateTime;\nDateTime.unixtime();\n该方法我们在实际业务中经常用到。以 \"msec sec\" 的格式返回当前 Unix 时间戳和微秒数:import com.buession.core.datetime.DateTime;\nDateTime.microtime();\n// 1657171717 948000\n该方法参考 PHP 的 microtime 函数而来。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/datetime.html#日期时间-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/id.html", + "children": [ + { + "title": "ID 生成器", + "url": "/manual/2.1/core/id.html#id-生成器", + "content": "ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。接口规范。public interface IdGenerator {\n\t/**\n\t * 获取下一个 ID\n\t *\n\t * @return ID\n\t */\n\tT nextId();\n\n}\n" + }, + { + "title": "ID 生成器", + "url": "/manual/2.1/core/id.html#id-生成器-id-生成器", + "content": "ID 生成器\n\n生成器\n说明\n\n\n\n\nAtomicSimpleIdGenerator\n基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nAtomicUUIDIdGenerator\n基于 AtomicLong 递增的,UUID ID 生成器\n\n\nNanoIDIdGenerator\njnanoid ID 生成器,可通过构造函数指定字符范围、长度\n\n\nRandomDigitIdGenerator\n随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定\n\n\nRandomIdGenerator\n随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位\n\n\nSimpleIdGenerator\n简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nSnowflakeIdGenerator\n雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值\n\n\nUUIDIdGenerator\nUUID ID 生成器\n\n\nimport com.buession.core.id.AtomicUUIDIdGenerator;import com.buession.core.id.NanoIDIdGenerator;\nimport com.buession.core.id.SnowflakeIdGenerator;\nimport com.buession.core.id.UUIDIdGenerator;\nimport com.buession.core.id.SimpleIdGenerator;\n\nAtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000001\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000002\n\nNanoIDIdGenerator idGenerator = new NanoIDIdGenerator();\nidGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK\n\nSnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();\nidGenerator.nextId(); // 170602258864545792\n\nUUIDIdGenerator idGenerator = new UUIDIdGenerator();\nidGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4\n\nSimpleIdGenerator idGenerator = new SimpleIdGenerator();\nidGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05\n注:关于雪花算法、jnanoid 算法的可自行搜索。\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/id.html#id-生成器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/math.html", + "children": [ + { + "title": "数学函数", + "url": "/manual/2.1/core/math.html#数学函数", + "content": "数学函数定义了实用的数学函数。\n\n方法\n说明\n\n\n\n\ncontinuousSum\n计算两个数之间连续相加之和\n\n\nrangeValue\n获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身\n\n\nimport com.buession.core.math.Math;\nlong result = Math.continuousSum(1, 100);\n// 5050\nimport com.buession.core.math.Math;\nlong value = 3;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 4\nimport com.buession.core.math.Math;\nlong value = 5;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 5\nimport com.buession.core.math.Math;\nlong value = 11;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 10\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/math.html#数学函数-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/serializer.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.1/core/serializer.html#构建器", + "content": "构建器对象的序列化和反序列化,包括二进制和 JSON。您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。" + }, + { + "title": "序列化、反序列化类", + "url": "/manual/2.1/core/serializer.html#构建器-序列化、反序列化类", + "content": "序列化、反序列化类\n\n类\n说明\n\n\n\n\nDefaultByteArraySerializer\n将对象序列化为二进制,或将二进制反序列化为对象\n\n\nFastJsonJsonSerializer\n基于 FastJSON 的对象与 JSON 之间的序列化和反序列化\n\n\nGsonJsonSerializer\n基于 Gson 的对象与 JSON 之间的序列化和反序列化\n\n\nJacksonJsonSerializer\n基于 Jackson2 的对象与 JSON 之间的序列化和反序列化\n\n\n通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象\nDefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象\nDefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化\n在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑\nFastJsonJsonSerializer、GsonJsonSerializer、JacksonJsonSerializer 可以通过参数 Class、TypeReference 指定返回的对象类型\ncom.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/serializer.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/validator.html", + "children": [ + { + "title": "验证器", + "url": "/manual/2.1/core/validator.html#验证器", + "content": "验证器数据验证器及其注解。该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。并提供对应的基于 javax.validation 的校验注解。验证是否为 null判断任意对象是否为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNull(obj);\n验证是否不为 null判断任意对象是否不为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNotNull(obj);\n判断字符串是否为空白字符串判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 falseimport com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\n\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\na\";\nboolean result = Validate.isBlank(str); // false\n注:isNotBlank 与之相反\n判断是否为空isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空import com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isEmpty(str); // true\n\nString str = \" \";\nboolean result = Validate.isEmpty(str); // false\n\nboolean result = Validate.isEmpty(new String[]{}); // true\n\nboolean result = Validate.isEmpty(new Integer[]{1}); // false\n注:isNotEmpty 与之相反\n判断是否在两个数之间isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3); // true\n\nboolean result = Validate.isBetween(2, 2, 3); // false\n可通过参数设置是否包含边界值import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3, true); // true\n\nboolean result = Validate.isBetween(2, 2, 3, true); // true\n判断是否为电话号码isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isTel(\"028-12345678\"); // true\n\nboolean result = Validate.isTel(\"028-02345678\"); // false\n判断是否为手机号码isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isMobile(\"028-12345678\"); // false\n\nboolean result = Validate.isMobile(\"13800138000\"); // true\n判断是否为邮政编码isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。import com.buession.core.validator.Validate;\nboolean result = Validate.isPostCode(\"043104\"); // false\n\nboolean result = Validate.isPostCode(\"643104\"); // true\n判断是否为 QQ 号码isQQ 可判断一个字符串是否为 QQ 号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isQQ(\"043104\"); // false\n\nboolean result = Validate.isQQ(\"25132.141\"); // true\n判断是否为身份证号码isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\", true, \"2.10-01-01\");\n其它,更多方法可以参考手册。" + }, + { + "title": "注解", + "url": "/manual/2.1/core/validator.html#验证器-注解", + "content": "注解javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。\n\n注解\n验证的数据类型\n说明\n\n\n\n\n@Alnum\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Alpha\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Numeric\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Between\nshort、int、double 等任何 Number 的子类型\n验证注解的元素值是否为在两个数之间\n\n\n@Empty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否为空\n\n\n@NotEmpty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否不为空\n\n\n@HasText\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@IDCard\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Ip\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Isbn\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@MimeType\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Mobile\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Null\n任意类型\n验证注解的元素值是否为 null\n\n\n@NotNull\n任意类型\n验证注解的元素值是否为 null\n\n\n@Port\nInteger\n验证注解的元素值是否为 null\n\n\n@PostCode\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@QQ\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Tel\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Xdigit\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/validator.html#验证器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/utils.html", + "children": [ + { + "title": "工具类", + "url": "/manual/2.1/core/utils.html#工具类", + "content": "工具类常用通用性工具类。" + }, + { + "title": "Byte 数组比较", + "url": "/manual/2.1/core/utils.html#工具类-byte-数组比较", + "content": "Byte 数组比较ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。" + }, + { + "title": "注解工具", + "url": "/manual/2.1/core/utils.html#工具类-注解工具", + "content": "注解工具AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class clazz, final Class[] annotations)、hasMethodAnnotationPresent(Method method, final Class[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。" + }, + { + "title": "断言", + "url": "/manual/2.1/core/utils.html#工具类-断言", + "content": "断言Assert 和 springframework 中的注解类似,不过逻辑有些相反。" + }, + { + "title": "Byte 工具", + "url": "/manual/2.1/core/utils.html#工具类-byte-工具", + "content": "Byte 工具ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。import com.buession.core.utils.ByteUtils;\nbyte[] bytes;\nchar c = ByteUtils.toChar(bytes);\n\nchar[] chars = ByteUtils.toChar(bytes);\nbyte 数组连接。import com.buession.core.utils.ByteUtils;\nbyte[] dest;\nbyte[] source\nbyte[] result = ByteUtils.concat(dest, source);\n" + }, + { + "title": "Character 工具", + "url": "/manual/2.1/core/utils.html#工具类-character-工具", + "content": "Character 工具CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。import com.buession.core.utils.CharacterUtils;\nchar c;\nbyte[] bytes = ByteUtils.toBytes(c);\n\nchar[] chars;\nbyte[] bytes = ByteUtils.toBytes(chars);\n" + }, + { + "title": "数字工具", + "url": "/manual/2.1/core/utils.html#工具类-数字工具", + "content": "数字工具NumberUtils 提供了对数字相关的操作。\n\n方法\n说明\n\n\n\n\nint2bytes\n将 int 转换为 byte[]\n\n\nbytes2int\n将 byte[] 转换为 int\n\n\nlong2bytes\n将 long 转换为 byte[]\n\n\nbytes2long\n将 byte[] 转换为 long\n\n\nfloat2bytes\n将 float 转换为 byte[]\n\n\nbytes2float\n将 byte[] 转换为 float\n\n\ndouble2bytes\n将 double 转换为 byte[]\n\n\nbytes2double\n将 byte[] 转换为 double\n\n\n" + }, + { + "title": "字符串工具", + "url": "/manual/2.1/core/utils.html#工具类-字符串工具", + "content": "字符串工具StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。截取字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.substr(\"abcde\", 1); // bcde\nString result = StringUtils.substr(\"abcde\", 1, 2); // bc\n生成随机字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.random(length);\n比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 3); // true\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 4); // false\n忽略大小写比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"Abce\", 3); // true\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"aBce\", 4); // false\n" + }, + { + "title": "拼音工具", + "url": "/manual/2.1/core/utils.html#工具类-拼音工具", + "content": "拼音工具PinyinUtils 封装了获取中文拼音、拼音首字母的方法。import com.buession.core.utils.PinyinUtils;\nString result = PinyinUtils.getPinyin(\"中国\"); // zhongguo\nString result = PinyinUtils.getPinYinFirstChar(\"中国\"); // zg\n" + }, + { + "title": "随机数工具", + "url": "/manual/2.1/core/utils.html#工具类-随机数工具", + "content": "随机数工具RandomUtils 封装了随机数的生成。\n\n方法\n说明\n\n\n\n\nnextBoolean\n随机布尔值\n\n\nnextBytes\n随机字节数组\n\n\nnextInt\n生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE\n\n\nnextLong\n生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE\n\n\nnextFloat\n生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE\n\n\nnextDouble\n生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE\n\n\n" + }, + { + "title": "Properties 工具", + "url": "/manual/2.1/core/utils.html#工具类-properties-工具", + "content": "Properties 工具PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。import com.buession.core.utils.SystemPropertyUtils;\nInteger result = PropertiesUtils.getInteger(properties, key);\nBoolean result = PropertiesUtils.getBoolean(properties, key);\n" + }, + { + "title": "System Property 工具", + "url": "/manual/2.1/core/utils.html#工具类-system-property-工具", + "content": "System Property 工具SystemPropertyUtils 封装了系统属性或系统环境变量的操作。设置属性方法 setProperty 对 System.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty。import com.buession.core.utils.SystemPropertyUtils;\nSystemPropertyUtils.setProperty(\"http.port\", 8080);\nSystemPropertyUtils.setProperty(\"http.ssl.enable\", false);\n获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。String value = System.getProperty(name);\nif(Validate.hasText(value) == false){\n value = System.getenv(name);\n}\n" + }, + { + "title": "版本工具", + "url": "/manual/2.1/core/utils.html#工具类-版本工具", + "content": "版本工具VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。import com.buession.core.utils.VersionUtils;\nVersionUtils.compare(\"1.0.0\", \"1.0.1-beta\"); // -1\nVersionUtils.compare(\"1.0.0\", \"1.0.0r\"); // -1\n规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本获取类的版本值import com.buession.core.utils.VersionUtils;\nByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.1.0\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/utils.html#工具类-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/other.html", + "children": [ + { + "title": "其它", + "url": "/manual/2.1/core/other.html#其它", + "content": "其它通用的接口定义,框架自身类,以及其它杂项。" + }, + { + "title": "框架自身工具", + "url": "/manual/2.1/core/other.html#其它-框架自身工具", + "content": "框架自身工具获取 Buession Framework 版本:import com.buession.core.Framework;import com.buession.core.BuesssionFrameworkVersion;\n\nBuesssionFrameworkVersion.getVersion(); // 2.1.0\nFramework.VERSION; // 2.1.0\n获取 Buession Framework 框架名称:import com.buession.core.Framework;\nFramework.NAME; // \"Buession\"\n" + }, + { + "title": "命令执行器", + "url": "/manual/2.1/core/other.html#其它-命令执行器", + "content": "命令执行器命令执行器接口:/** * 命令执行器\n *\n * @param \n * \t\t命令上下文\n * @param \n * \t\t命令执行返回值\n */\n@FunctionalInterface\npublic interface Executor {\n\n\t/**\n\t * 命令执行\n\t *\n\t * @param context\n\t * \t\t命令执行器上下文\n\t *\n\t * @return 命令执行返回值,R 类型的实例\n\t */\n\tR execute(C context);\n\n}\n您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。" + }, + { + "title": "销毁接口", + "url": "/manual/2.1/core/other.html#其它-销毁接口", + "content": "销毁接口功能类似 java.io.Closeable。public interface Destroyable {\n\t/**\n\t * 销毁相关资源\n\t *\n\t * @throws IOException\n\t * \t\tIO 错误时抛出\n\t */\n\tvoid destroy() throws IOException;\n\n}\n" + }, + { + "title": "Rawable", + "url": "/manual/2.1/core/other.html#其它-rawable", + "content": "Rawable原始的,约定实现该接口的类,必须返回原始字节数组。public interface Rawable {\n\t/**\n\t * 返回原始的字节数组\n\t *\n\t * @return 原始的字节数组\n\t */\n\tbyte[] getRaw();\n\n}\n" + }, + { + "title": "名称节点", + "url": "/manual/2.1/core/other.html#其它-名称节点", + "content": "名称节点名称节点,约定实现该接口的类应该返回一个名称public interface NamedNode {\n\t/**\n\t * 返回节点名称\n\t *\n\t * @return 节点名称\n\t */\n\t@Nullable\n\tString getName();\n\n}\n" + }, + { + "title": "分页", + "url": "/manual/2.1/core/other.html#其它-分页", + "content": "分页com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.1/core/exception.html", + "children": [ + { + "title": "异常", + "url": "/manual/2.1/core/exception.html#异常", + "content": "异常通用异常的定义。\n\n异常\n说明\n\n\n\n\nAccessException\n拒绝访问异常\n\n\nClassInstantiationException\n类实例化异常\n\n\nConversionException\n数据类型转换异常\n\n\nDataAlreadyExistException\n数据已存在异常\n\n\nDataNotFoundException\n数据不存在或未找到异常\n\n\nInsteadException\n类方法废弃后,需要使用其它类库方法来替代\n\n\nNestedRuntimeException\n嵌套运行时异常\n\n\nOperationException\n运算异常\n\n\nPresentException\n--\n\n\nSerializationException\n序列化异常\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/core/exception.html#异常-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-cron 参考手册", + "content": "对 quartz 的二次封装", + "url": "/manual/2.1/cron/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/cron/index.html#安装", + "content": "安装 com.buession\n buession-cron\n x.x.x\n\n由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。且在今后的版本中,该模块可能会被废弃。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/cron/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。", + "url": "/manual/2.1/dao/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/dao/index.html#安装", + "content": "安装 com.buession\n buession-dao\n x.x.x\n\n我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。同时,我们在代码层面实现了数据库的读写分离。我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。" + }, + { + "title": "Dao 接口", + "url": "/manual/2.1/dao/index.html#dao-接口", + "content": "Dao 接口接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.1.0/com/buession/dao/Dao.htmlpublic interface Dao {}\nP:主键类型\nE:实体类\n分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset。条件为 Map 类型,允许为 null。排序为 Map 类型,允许为 null。MyBatisBuession Framework 扩展 MyBatis 的文档。MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/dao/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.1/dao/mybatis.html", + "children": [ + { + "title": "MyBatis", + "url": "/manual/2.1/dao/mybatis.html#mybatis", + "content": "MyBatisBuession Framework 扩展 MyBatis 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.1/dao/mybatis.html#mybatis-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplate、slaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。" + }, + { + "title": "Mybatis 约定", + "url": "/manual/2.1/dao/mybatis.html#mybatis-mybatis-约定", + "content": "Mybatis 约定如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace\nnamespace com.buession.dao.test.dao;\npublic class UserDaoImpl extends AbstractMyBatisDao {\n\n\t@Override\n\tprotected String getStatement(){\n\t\treturn \"com.buession.dao.test.dao.UserMapper\";\n\t}\n\n}\n\n\nMapper 的 SQL ID 和方法名保持一致\n\n\nSQL ID\n说明\n返回值\n\n\n\n\ninsert\n插入数据\n影响的行数\n\n\nbatchInsert\n批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入\n每次插入影响的行数列表\n\n\nreplace\n替换数据,即:REPLACE 语句\n影响的行数\n\n\nbatchReplace\n批量替换数据,即:REPLACE 语句\n每次替换数据影响的行数列表\n\n\nupdate\n更新数据\n更新条数\n\n\nupdateByPrimary\n根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值\n更新条数\n\n\ngetByPrimary\n根据主键查询数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselectOne\n(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselect\n查询数据\n数据结果列表\n\n\ngetAll\n查询所有数据\n数据结果列表\n\n\ncount\n获取记录数\n记录数\n\n\ndeleteByPrimary\n根据主键删除数据\n影响条数\n\n\ndelete\n删除数据\n影响条数\n\n\nclear\n清除数据\n影响条数\n\n\ntruncate\n截断数据\n影响条数\n\n\n注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。\n以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。\n" + }, + { + "title": "Mybatis 类型处理器", + "url": "/manual/2.1/dao/mybatis.html#mybatis-mybatis-类型处理器", + "content": "Mybatis 类型处理器MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao。\n\nTypeHandler\n说明\n\n\n\n\nDefaultEnumTypeHandler\n默认 Enum 类型处理器,将值直接转换为枚举字段\n\n\nIgnoreCaseEnumTypeHandler\n忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段\n\n\nDefaultJsonTypeHandler\nJSON 处理器,将 JSON 格式的字符串值和类型 进行转换\n\n\nDefaultSetEnumTypeHandler\n默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素\n\n\nIgnoreCaseSetEnumTypeHandler\n忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素\n\n\nDefaultSetTypeHandler\n默认 Set 类型处理器,将值以 \",\" 拆分转换为 Set\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/dao/mybatis.html#mybatis-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.1/dao/mongodb.html", + "children": [ + { + "title": "MongoDB", + "url": "/manual/2.1/dao/mongodb.html#mongodb", + "content": "MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.1/dao/mongodb.html#mongodb-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplate、slaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。AbstractMongoDBDao 的 replace 执行的也是 insert。在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/dao/mongodb.html#mongodb-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-geoip 参考手册", + "content": "对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。", + "url": "/manual/2.1/geoip/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/geoip/index.html#安装", + "content": "安装 com.buession\n buession-geoip\n x.x.x\n\n通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2.11:0DB8:0000:0023:0008:0800:2.1C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。" + }, + { + "title": "获取国家信息", + "url": "/manual/2.1/geoip/index.html#获取国家信息", + "content": "获取国家信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nCountry country = resolver.country(\"114.114.114.114\");\n// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}\n\nCountry country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8\n// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}\n" + }, + { + "title": "获取城市信息", + "url": "/manual/2.1/geoip/index.html#获取城市信息", + "content": "获取城市信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nDistrict district = resolver.district(\"114.114.114.114\");\n// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}\n\nDistrict district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8\n// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}\n" + }, + { + "title": "获取位置信息", + "url": "/manual/2.1/geoip/index.html#获取位置信息", + "content": "获取位置信息位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nLocation location = resolver.location(\"114.114.114.114\");\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=18062.1, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.1617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Shanghai\",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}\n\nLocation location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=2.1}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Seoul\",offset=32.10000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}\n" + }, + { + "title": "缓存", + "url": "/manual/2.1/geoip/index.html#缓存", + "content": "缓存为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。" + }, + { + "title": "Resolver 的 Spring Factory Bean", + "url": "/manual/2.1/geoip/index.html#resolver-的-spring-factory-bean", + "content": "Resolver 的 Spring Factory Bean我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。dbPath 和 stream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。\nenableCache 可以控制是否缓存。\n" + }, + { + "title": "关于 IP 库", + "url": "/manual/2.1/geoip/index.html#关于-ip-库", + "content": "关于 IP 库buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/geoip/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。", + "url": "/manual/2.1/httpclient/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/httpclient/index.html#安装", + "content": "安装 com.buession\n buession-httpclient\n x.x.x\n\n我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponents 和 okhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。传统的方式: org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\nimport org.apache.http.HttpResponse;import org.apache.http.conn.HttpClientConnectionManager;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.client.methods.HttpPost;\n\nHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();\n\nHttpResponse response = httpClient.execute(new HttpPost(\"https://www.buession.com/\"));\n或者 com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport okhttp3.HttpClientConnectionManager;import okhttp3.OkHttpClient;\nimport okhttp3.ConnectionPool;\nimport okhttp3.Request;\nimport okhttp3.Request.Builder;\nimport okhttp3.Response;\n\nOkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());\nHttpClient httpClient = builder.build();\n\nBuilder requestBuilder = new Builder().post();\nrequestBuilder.url(\"https://www.buession.com/\");\nRequest okHttpRequest = requestBuilder.build();\n\nResponse httpResponse = httpClient.newCall(okHttpRequest).execute();\n现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。 com.buession\n buession-httpclient\n x.x.x\n\n\n org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\n或者 com.buession\n buession-httpclient\n x.x.x\n\n\n com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.OkHttpHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.conn.OkHttpClientConnectionManager;\nimport com.buession.httpclient.core.Response;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\n" + }, + { + "title": "展望", + "url": "/manual/2.1/httpclient/index.html#展望", + "content": "展望目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.2) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/httpclient/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.1/httpclient/configuration.html", + "children": [ + { + "title": "连接配置", + "url": "/manual/2.1/httpclient/configuration.html#连接配置", + "content": "连接配置您可以通过连接配置类 Configuration 配置 apache httpcomponents 和 okhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponents 或 okhttp3 的配置信息。" + }, + { + "title": "配置属性说明", + "url": "/manual/2.1/httpclient/configuration.html#连接配置-配置属性说明", + "content": "配置属性说明\n\n属性名称\n数据类型\napache httpcomponents 对应配置\nokhttp3 对应配置\n默认值\n说明\n\n\n\n\nmaxConnections\nint\nmaxTotal\nmaxIdleConnections\n5000\n最大连接数\n\n\nmaxPerRoute\nint\ndefaultMaxPerRoute\n--\n500\n每个路由的最大连接数\n\n\nidleConnectionTime\nint\ncloseIdleConnections\nkeepAliveDuration\n60000\n空闲连接存活时长(单位:毫秒)\n\n\nconnectTimeout\nint\nconnectTimeout\nconnectTimeout\n3000\n连接超时时间(单位:毫秒)\n\n\nconnectionRequestTimeout\nint\nconnectionRequestTimeout\n--\n5000\n从连接池获取连接的超时时间(单位:毫秒)\n\n\nreadTimeout\nint\nsocketTimeout\nreadTimeout\n5000\n读取超时时间(单位:毫秒)\n\n\nallowRedirects\nBoolean\nredirectsEnabled\nfollowRedirects\n--\n是否允许重定向\n\n\nrelativeRedirectsAllowed\nBoolean\nrelativeRedirectsAllowed\n--\n--\n是否应拒绝相对重定向\n\n\ncircularRedirectsAllowed\nBoolean\ncircularRedirectsAllowed\n--\n--\n是否允许循环重定向\n\n\nmaxRedirects\nInteger\nmaxRedirects\n--\n--\n最大允许重定向次数\n\n\nauthenticationEnabled\nboolean\nauthenticationEnabled\n--\n--\n是否开启 Http Basic 认证\n\n\ncontentCompressionEnabled\nboolean\ncontentCompressionEnabled\n--\n--\n是否启用内容压缩\n\n\nnormalizeUri\nboolean\nnormalizeUri\n--\n--\n是否标准化 URI\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/httpclient/configuration.html#连接配置-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.1/httpclient/connectionmanager.html", + "children": [ + { + "title": "连接管理器", + "url": "/manual/2.1/httpclient/connectionmanager.html#连接管理器", + "content": "连接管理器连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponents 和 okhttp3 的文档。您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponents 或 okhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。" + }, + { + "title": "关于 okhttp 连接管理器", + "url": "/manual/2.1/httpclient/connectionmanager.html#连接管理器-关于-okhttp-连接管理器", + "content": "关于 okhttp 连接管理器okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/httpclient/connectionmanager.html#连接管理器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.1/httpclient/response.html", + "children": [ + { + "title": "响应", + "url": "/manual/2.1/httpclient/response.html#响应", + "content": "响应当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。buession-httpclient 会将 apache httpcomponents 或 okhttp3 的响应对象,转换为 Response。需要注意的是,原生 apache httpcomponents 或 okhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。import com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.core.Response;\nimport java.io.InputStream;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\nInputStream stream = response.getInputStream(); // 以流的形式获取响应体\nString body = response.getBody(); // 以字符串的形式获取响应体\n\nstream.close();\ngetInputStream、getBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponents 或 okhttp3 返回的流。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/httpclient/response.html#响应-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.1/httpclient/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.1/httpclient/method.html#方法", + "content": "方法buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。示例:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\");\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\");\n您可以自定义请求头:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.List;\nimport java.util.ArrayList;\n\nList headers = new ArrayList();\n\nheaders.add(new Header(\"X-SDK-NAME\", \"Buession\"));\nheaders.add(new Header(\"X-Timestamp\", System.currentTimeMillis()));\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", headers);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", headers);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", headers);\n您可以设置请求参数:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.Map;\nimport java.util.HashMap;\n\nMap parameters = new HashMap();\n\nparameters.put(\"action\", \"edit\");\nparameters.put(\"id\", 1);\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", parameters);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", parameters);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", parameters);\n您可以设置请求体:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport jcom.buession.httpclient.core.RequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\n\nEncodedFormRequestBody requestBody = new EncodedFormRequestBody();\n\nrequestBody.addRequestBodyElement(\"username\", \"buession\");\nrequestBody.addRequestBodyElement(\"password\", \"buession\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", requestBody);\n\nJsonRawRequestBody requestBody = new JsonRawRequestBody(new User());\n// PUT 请求\nResponse response = httpClient.put(\"https://www.buession.com/\", requestBody);\n不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody。" + }, + { + "title": "RequestBody", + "url": "/manual/2.1/httpclient/method.html#方法-requestbody", + "content": "RequestBody\n\nRequestBody\nContent-Type\n说明\n\n\n\n\nInputStreamRequestBody\napplication/octet-stream\n二进制请求体\n\n\nChunkedInputStreamRequestBody\napplication/octet-stream\nChunked 二进制请求体\n\n\nRepeatableInputStreamRequestBody\napplication/octet-stream\nRepeatable 二进制请求体\n\n\nEncodedFormRequestBody\napplication/x-www-form-urlencoded\n普通表单请求体\n\n\nMultipartFormRequestBody\nmultipart/form-data\n文件上传表单请求体\n\n\nHtmlRawRequestBody\ntext/html\nHTML 请求体\n\n\nJavaScriptRawRequestBody\napplication/javascript\nJavaScript 请求体\n\n\nJsonRawRequestBody\napplication/json\nJSON 请求体\n\n\nTextRawRequestBody\ntext/plain\nTEXT 请求体\n\n\nXmlRawRequestBody\ntext/xml\nXML 请求体\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/httpclient/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-io 参考手册", + "content": "封装了对文件的操作", + "url": "/manual/2.1/io/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/io/index.html#安装", + "content": "安装 com.buession\n buession-io\n x.x.x\n\n该模块二次封装了 java java.io.File 和 java.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。" + }, + { + "title": "读取文件", + "url": "/manual/2.1/io/index.html#读取文件", + "content": "读取文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nbyte[] result = file.read();\n" + }, + { + "title": "写文件", + "url": "/manual/2.1/io/index.html#写文件", + "content": "写文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nfile.write(\"Buession\");\nfile.write(\"Buession\".getBytes());\nfile.write(\"Buession\", true); // 追加写\n" + }, + { + "title": "获取文件 MD5、SHA-1值", + "url": "/manual/2.1/io/index.html#获取文件-md5、sha-1值", + "content": "获取文件 MD5、SHA-1值import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nString md5 = file.getMd5(); // 获取文件 MD5\nString sha1 = file.getSha1(); // 获取文件 SHA-1\n" + }, + { + "title": "获取文件 MD5、SHA-1 值", + "url": "/manual/2.1/io/index.html#获取文件-md5、sha-1-值", + "content": "获取文件 MD5、SHA-1 值import com.buession.io.file.File;import com.buession.io.MimeType;\n\nFile file = new File(\"/tmp/debug.txt\");\n\nMimeType result = file.getMimeType();\n" + }, + { + "title": "设置文件权限", + "url": "/manual/2.1/io/index.html#设置文件权限", + "content": "设置文件权限import com.buession.io.file.Files;\nFiles.chmod(\"/tmp/debug.txt\", 0777);\n" + }, + { + "title": "设置文件用户组", + "url": "/manual/2.1/io/index.html#设置文件用户组", + "content": "设置文件用户组import com.buession.io.file.Files;\nFiles.chgrp(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "设置文件用户", + "url": "/manual/2.1/io/index.html#设置文件用户", + "content": "设置文件用户import com.buession.io.file.Files;\nFiles.chown(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "注解", + "url": "/manual/2.1/io/index.html#注解", + "content": "注解注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。import com.buession.io.json.annotation.MimeTypeString;\nclass File {\n\n @MimeTypeString\n private MimeType mime;\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/io/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-jdbc 参考手册", + "content": "JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。", + "url": "/manual/2.1/jdbc/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/jdbc/index.html#安装", + "content": "安装 com.buession\n buession-jdbc\n x.x.x\n\n通过提供的 API,您可以简化对 DBCP2、Druid、Hikari、Tomcat 数据源的初始化,该类库基本不单独使用。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/jdbc/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-json 参考手册", + "content": "主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。", + "url": "/manual/2.1/json/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/json/index.html#安装", + "content": "安装 com.buession\n buession-json\n x.x.x\n\n封装了大量基于 jackson 的注解。" + }, + { + "title": "注解", + "url": "/manual/2.1/json/index.html#注解", + "content": "注解\n\n注解\n说明\n\n\n\n\nCalendarUnixTimestamp\njava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar\n\n\nDateUnixTimestamp\njava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date\n\n\nSqlDateUnixTimestamp\njava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date\n\n\nTimestampUnixTimestamp\njava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp\n\n\nJsonEnum2Map\n枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举\n\n\nSensitive\n通过该注解可以实现数据的脱敏\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/json/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-lang 参考手册", + "content": "常用 POJO 类和枚举的定义,详细查看 API 参考手册。", + "url": "/manual/2.1/lang/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/lang/index.html#安装", + "content": "安装 com.buession\n buession-lang\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/lang/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-net 参考手册", + "content": "网络相关工具类。", + "url": "/manual/2.1/net/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/net/index.html#安装", + "content": "安装 com.buession\n buession-net\n x.x.x\n\n" + }, + { + "title": "IP 地址工具类", + "url": "/manual/2.1/net/index.html#ip-地址工具类", + "content": "IP 地址工具类IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。import com.buession.net.utils.InetAddressUtis;\nlong result = InetAddressUtis.ip2long(\"127.0.0.1\"); // 2130706433\nString ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1\nURI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/net/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。", + "url": "/manual/2.1/redis/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/redis/index.html#安装", + "content": "安装 com.buession\n buession-redis\n x.x.x\n\n" + }, + { + "title": "介绍", + "url": "/manual/2.1/redis/index.html#介绍", + "content": "介绍buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。import com.buession.redis.RedisTemplate;import com.buession.redis.core.Options;\nimport com.buession.core.serializer.type.TypeReference;\nimport java.utils.Map;\nimport java.utils.HashMap;\n\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.setOptions(new Options());\nredisTemplate.afterPropertiesSet();\n\n// 将 User 对象写进 key 为 user hash 中\nredisTemplate.hSet(\"user\", \"1\", new User());\n\n// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User\nUser user = redisTemplate.hGetObject(\"user\", \"1\", User.class);\n\n// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User\nMap data = redisTemplate.hGetAllObject(\"user\", \"1\", new TypeReference>{});\n" + }, + { + "title": "展望", + "url": "/manual/2.1/redis/index.html#展望", + "content": "展望目前,buession-redis 仅支持 jedis,不支持 lettuce,我们预计会在下个版本或者下下个版本(即:2.1 或者 2.2)中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/redis/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.1/redis/datasource.html", + "children": [ + { + "title": "数据源", + "url": "/manual/2.1/redis/datasource.html#数据源", + "content": "数据源buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。数据源 DataSource 包括三个子接口:StandaloneDataSource:单机模式数据源\nSentinelDataSource:哨兵模式数据源\nClusterDataSource:集群模式数据源\njedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。测试环境 properties:redis.host=127.0.0.1redis.port=6379\nredis.password=\n生产环境 properties:redis.host=192.168.100.131redis.port=6379\nredis.password=passwd\n" + }, + { + "title": "连接池", + "url": "/manual/2.1/redis/datasource.html#数据源-连接池", + "content": "连接池通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfig 和 ConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。连接池配置\n\n配置项\n数据类型\n-- 默认值\n说明\n\n\n\n\nlifo\nboolean\nGenericObjectPoolConfig.DEFAULT_LIFO\n池模式,为 true 时,后进先出;为 false 时,先进先出\n\n\nfairness\nboolean\nGenericObjectPoolConfig.DEFAULT_FAIRNESS\n当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制\n\n\nmaxWait\nDuration\nGenericObjectPoolConfig.DEFAULT_MAX_WAIT\n当连接池资源用尽后,调用者获取连接时的最大等待时间\n\n\nminEvictableIdleTime\nDuration\n60000\n连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除\n\n\nsoftMinEvictableIdleTime\nDuration\nGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION\n连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数\n\n\nevictionPolicyClassName\nString\nGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME\n驱逐策略的类名\n\n\nevictorShutdownTimeout\nDuration\nGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT\n关闭驱逐线程的超时时间\n\n\nnumTestsPerEvictionRun\nint\n-1\n检测空闲对象线程每次运行时检测的空闲对象的数量\n\n\ntestOnCreate\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE\n在创建对象时检测对象是否有效,配置 true 会降低性能\n\n\ntestOnBorrow\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW\n在从对象池获取对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestOnReturn\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN\n在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestWhileIdle\nboolean\ntrue\n在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\nblockWhenExhausted\nboolean\nGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED\n当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\njmxEnabled\nboolean\nGenericObjectPoolConfig.DEFAULT_JMX_ENABLE\n是否注册 JMX\n\n\njmxNamePrefix\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX\nJMX 前缀\n\n\njmxNameBase\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE\n使用 base + jmxNamePrefix + i 来生成 ObjectName\n\n\nmaxTotal\nint\nGenericObjectPoolConfig.DEFAULT_MAX_TOTAL\n最大连接数\n\n\nminIdle\nint\nGenericObjectPoolConfig.DEFAULT_MIN_IDLE\n最小空闲连接数\n\n\nmaxIdle\nint\nGenericObjectPoolConfig.DEFAULT_MAX_IDLE\n最大空闲连接数\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/redis/datasource.html#数据源-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.1/redis/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.1/redis/method.html#方法", + "content": "方法buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。import com.buession.redis.BaseRedisTemplate;\nBaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 删除哈希表 key 中的一个或多个指定域\nredisTemplate.hDel(\"user\", \"1\", \"2\", \"3\");\n\n// 检查给定 key 是否存在\nredisTemplate.exists(\"user\");\n\n// 获取列表 key 中,下标为 index 的元素\nredisTemplate.lIndex(\"user\", 1);\n\n// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾\nredisTemplate.append(\"key\", \"value 1\");\nBaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。import com.buession.redis.RedisTemplate;\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类\nUser user = redisTemplate.lIndexObject(\"user\", 1, User.class);\n序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/redis/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-session 参考手册", + "content": "无文档", + "url": "/manual/2.1/session/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/session/index.html#安装", + "content": "安装 com.buession\n buession-session\n x.x.x\n\n该模块无实际意义,将在今后的版本中会删除掉。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/session/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-thesaurus 参考手册", + "content": "对词库的解析,目前仅支持搜狗词条。", + "url": "/manual/2.1/thesaurus/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/thesaurus/index.html#安装", + "content": "安装 com.buession\n buession-thesaurus\n x.x.x\n\n您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。import com.buession.thesaurus.SogouParser;import com.buession.thesaurus.Parser;\nimport com.buession.thesaurus.core.Word;\nimport java.util.Set;\n\nParser parser = new SogouParser();\n\nSet words parser.parse(\"搜谱拼音词条文件路径\");\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/thesaurus/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-velocity 参考手册", + "content": "spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。", + "url": "/manual/2.1/velocity/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/velocity/index.html#安装", + "content": "安装 com.buession\n buession-velocity\n x.x.x\n\n该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/velocity/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。", + "url": "/manual/2.1/web/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.1/web/index.html#安装", + "content": "安装 com.buession\n buession-web\n x.x.x\n\nbuession-web 扩展了 spring-webmvc、spring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.1/web/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.1/web/annotation.html", + "children": [ + { + "title": "注解", + "url": "/manual/2.1/web/annotation.html#注解", + "content": "注解我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。" + }, + { + "title": "注解", + "url": "/manual/2.1/web/annotation.html#注解-注解", + "content": "注解\n\n注解\nRequest / Response\n作用域\n说明\n\n\n\n\n@RequestClientIp\nrequest\n方法参数\n获取当前请求的客户端 IP 地址,支持返回字符串、long 类型的 IP 地址,以及 InetAddress\n\n\n@ContentType\nresponse\n类、方法\n设置响应 Content-Type\n\n\n@HttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值\n\n\n@DisableHttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存\n\n\n@ResponseHeader\nresponse\n类、方法\n设置响应头\n\n\n@ResponseHeaders\nresponse\n类、方法\n批量设置响应头\n\n\n@DocumentMetaData\nresponse\n类、方法\n设置页面标题、页面编码、关键字、描述、版权等等元信息\n\n\n获取用户端真实 IP@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n您也可以指定获取用户真实 IP 的请求头列表,若未指定则使用 RequestUtils.getClientIp(request) 方法获取,获取顺序参考:RequestUtils.CLIENT_IP_HEADERS@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/ip1\")\n\t@ResponseBody\n\tpublic String ip1(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip2\")\n\t@ResponseBody\n\tpublic Long ip2(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) long ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n\t@RequestMapping(path = \"/ip3\")\n\t@ResponseBody\n\tpublic InetAddress ip3(@RequestClientIp(headerName = {\"X-Real-Ip\", \"X-User-Real-Ip\"}) InetAddress ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n设置页面缓存@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n以上,会自动计算 Cache-Control 和 pragma 的值。当然,您也可以手动指定。@Controller@RequestMapping(path = \"/test\")\npublic class TestController {\n\n\t@RequestMapping(path = \"/cache\")\n\t@HttpCache(expires = \"5\", cacheControl=\"public, max-age=5\")\n\t@ResponseBody\n\tpublic String cache(@RequestClientIp String ip, ServerHttpResponse response){\n\t\treturn ip;\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.1/web/filter.html", + "children": [ + { + "title": "过滤器", + "url": "/manual/2.1/web/filter.html#过滤器", + "content": "过滤器我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。" + }, + { + "title": "过滤器", + "url": "/manual/2.1/web/filter.html#过滤器-过滤器", + "content": "过滤器\n\n过滤器\n说明\n\n\n\n\nPoweredByFilter\nPowered By 过滤器\n\n\nPrintUrlFilter\n打印当前请求 URL 过滤器\n\n\nResponseHeaderFilter\n响应头过滤器,设置响应头\n\n\nResponseHeadersFilter\n响应头过滤器,批量设置响应头\n\n\nServerInfoFilter\nServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点\n\n\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.1/web/restful.html", + "children": [ + { + "title": "RESTFUL", + "url": "/manual/2.1/web/restful.html#restful", + "content": "RESTFULRestful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。@RestController@RequestMapping(path = \"/example\")\npublic class ExampleController extends AbstractRestController {\n\n\t@Override\n\tpublic Response add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){\n\t\t\n\t}\n\n\t@Override\n\tpublic Response edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id, @RequestBody ExampleDto example){\n\n\t}\n\n\t@Override\n\tpublic Response detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\n\t}\n\n\t@Override\n\tpublic Response delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.1/web/utils.html", + "children": [ + { + "title": "工具", + "url": "/manual/2.1/web/utils.html#工具", + "content": "工具我们封装了一些 web 相关的工具类,用于处理 request、response。servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。获取客户端真实 IP 地址:RequestUtils.getClientIp(request);我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr是否是 Ajax 请求:RequestUtils.isAjaxRequest(request);是否是移动设备请求:RequestUtils.isMobile(request);设置缓存:ResponseUtils.httpCache(response, 5); // 缓存 5 秒ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点\n" + } + ] + }, + { + "title": "API 参考手册", + "content": "Buession Framework API 包含以下目录:\n\n模块\n使用帮助\n手册\n\n\n\n\nbuession-aop\n使用帮助\nAPI 手册\n\n\nbuession-beans\n使用帮助\nAPI 手册\n\n\nbuession-core\n使用帮助\nAPI 手册\n\n\nbuession-cron\n使用帮助\nAPI 手册\n\n\nbuession-dao\n使用帮助\nAPI 手册\n\n\nbuession-geoip\n使用帮助\nAPI 手册\n\n\nbuession-httpclient\n使用帮助\nAPI 手册\n\n\nbuession-io\n使用帮助\nAPI 手册\n\n\nbuession-jdbc\n使用帮助\nAPI 手册\n\n\nbuession-json\n使用帮助\nAPI 手册\n\n\nbuession-lang\n使用帮助\nAPI 手册\n\n\nbuession-net\n使用帮助\nAPI 手册\n\n\nbuession-redis\n使用帮助\nAPI 手册\n\n\nbuession-session\n使用帮助\nAPI 手册\n\n\nbuession-thesaurus\n使用帮助\nAPI 手册\n\n\nbuession-velocity\n使用帮助\nAPI 手册\n\n\nbuession-web\n使用帮助\nAPI 手册\n\n\n", + "url": "/manual/2.0/index.html", + "children": [] + }, + { + "title": "buession-aop 参考手册", + "content": "AOP 封装,方便实现自定义注解", + "url": "/manual/2.0/aop/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/aop/index.html#安装", + "content": "安装 com.buession\n buession-aop\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/aop/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-beans 参考手册", + "content": "该类库提供了基于 commons-beanutils 和 cglib(spring-core 包中,名称空间:org.springframework.cglib.beans) 的 bean 工具的二次封装。包括了属性拷贝和属性映射,以及对象属性转换为 Map。", + "url": "/manual/2.0/beans/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/beans/index.html#安装", + "content": "安装 com.buession\n buession-beans\n x.x.x\n\n" + }, + { + "title": "属性拷贝", + "url": "/manual/2.0/beans/index.html#属性拷贝", + "content": "属性拷贝使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 和 target 的属性做映射,实现同名拷贝。import com.buession.beans.BeanUtils;\nBeanUtils.copyProperties(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.copyProperties(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "属性映射", + "url": "/manual/2.0/beans/index.html#属性映射", + "content": "属性映射使用此方法,可以实现两个对象之间的属性拷贝,该方式基于 BeanCopier 实现属性的拷贝。如果 source 对象是 Map 接口的实现,则会将 Map 的 key 转换为驼峰格式后和 target 的属性做映射,实现拷贝,这是 populate 和 copyProperties 的唯一区别。import com.buession.beans.BeanUtils;\nBeanUtils.populate(target, source)\n注:该方式只会对同类型的属性进行拷贝。即假设,source 中有数据类型为 int 名称为 id 的属性,target 中有数据类型为 long 名称为 id 的属性,是不会将 source 属性 id 的值拷贝到 target 的 id 属性上。\n我们可以指定 Converter 实现自定义规则进行属性拷贝。该方式的缺点是:BeanCopier 只使用 Converter 定义的规则去拷贝属性,所以在 convert 方法中要考虑所有的属性import com.buession.beans.BeanUtils;import org.springframework.cglib.core.Converter;\n\nBeanUtils.populate(target, source, new Converter() {\n\n\t@Override\n\tpublic Object convert(Object sourceFieldValue, Class targetFieldType, Object targetSetter){\n\t\tif(sourceFieldValue instanceof Short || sourceFieldValue instanceof Integer){\n\t\t\tif(targetFieldType.isAssignableFrom(Long.class) || targetFieldType.isAssignableFrom(long.class)){\n\t\t\t\treturn sourceFieldValue;\n\t\t\t}\n\t\t}else if(sourceFieldValue.getClass().isAssignableFrom(targetFieldType)){\n\t\t\treturn sourceFieldValue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n});\n" + }, + { + "title": "Bean 转换为 Map", + "url": "/manual/2.0/beans/index.html#bean-转换为-map", + "content": "Bean 转换为 Map使用此方法,可以实现将一个 bean 对象转换为 Map,bean 的属性作为 Map 的 Keyimport com.buession.beans.BeanUtils;\nMap result = BeanUtils.toMap(bean)\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/beans/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "该类库为核心包,包括构建器、工具类、数据验证、ID 生成器、转换器、序列化、数学方法、消息注入、Manager 层注解、日期时间类和各通用接口定义。", + "url": "/manual/2.0/core/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/core/index.html#安装", + "content": "安装 com.buession\n buession-core\n x.x.x\n\n构建器Map、集合的便捷式构建,减少您的代码行数编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中收集器数组、Map、集合的工具类上下文定义应用上下文的类库、注解转换器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。日期时间日期、时间工具ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。数学函数定义了实用的数学函数序列化和反序列化对象的序列化和反序列化,包括二进制和 JSON。验证器数据验证器及其注解工具类常用通用性工具类其它通用的接口定义,框架自身类异常通用异常的定义" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/builder.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.0/core/builder.html#构建器", + "content": "构建器Map、集合的便捷式构建,减少您的代码行数。您需要往 Map、List 中添加元素的传统写法是:import java.util.ArrayList;import java.util.List;\nimport java.util.HashMap;\nimport java.util.Map;\n\nList list = new ArrayList();\nlist.add(\"A\");\nlist.add(\"B\");\nlist.add(\"C\");\n\nMap map = new HashMap();\nmap.put(\"a\", \"A\");\nmap.put(\"b\", \"B\");\nmap.put(\"c\", \"C\");\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.Map;\n\nList list = ListBuilder.create().add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create().put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n此时的 List、Map 的实现类是 java.util.ArrayList、java.util.HashMap;您可以通过 create 指定其实现类。import com.buession.core.builder.ListBuilder;import com.buession.core.builder.MapBuilder;\nimport java.util.List;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.LinkedHashMap;\n\nList list = ListBuilder.create(LinkedList.class).add(\"A\").add(\"B\").add(\"C\").build();\n\nMap map = MapBuilder.create(LinkedHashMap.class).put(\"a\", \"A\").put(\"b\", \"B\").put(\"c\", \"C\");\n注:实现类必须为 public,且必须有无参数的访问权限为 public 的构造函数\n当您有 value 为 null 时,不添加到 List 时,传统写法:import java.util.ArrayList;import java.util.List;\n\nString value = null;\nList list = new ArrayList();\n\nif(value != null){\n\tlist.add(value);\n}\n而当您使用 buession framework 可以这么写:import com.buession.core.builder.ListBuilder;import java.util.List;\n\nString value = null;\nList list = ListBuilder.create().addIfPresent(value).build();\nMap、Set、Queue 同理。" + }, + { + "title": "便捷方法", + "url": "/manual/2.0/core/builder.html#构建器-便捷方法", + "content": "便捷方法\n\n方法\n说明\n\n\n\n\n List ListBuilder.epmty()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of()\n创建空的 V 类型的 List 对象\n\n\n List ListBuilder.of(V value)\n创建仅有一个元素的 V 类型的 List 对象\n\n\n Queue QueueBuilder.epmty()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of()\n创建空的 V 类型的 Queue 对象\n\n\n Queue QueueBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Queue 对象\n\n\n Set SetBuilder.epmty()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of()\n创建空的 V 类型的 Set 对象\n\n\n Set SetBuilder.of(V value)\n创建仅有一个元素的 V 类型的 Set 对象\n\n\n Map MapBuilder.epmty()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of()\n创建空的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\n Map MapBuilder.of(V value)\n创建仅有一个元素的 Key 为 K 类型,值为 V 类型的 Map 对象\n\n\nempty 与 jdk Collections.emptyList()、Collections.emptySet()、Collections.emptyMap() 的本质区别是返回的 List 实例不同,该方法创建的 List、Set、Map 实例是允许对 List、Set、Map 做操作,而 jdk Collections 创建的 List、Set、Map 则是不允许的。两个 of 方法均同理。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/builder.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/codec.html", + "children": [ + { + "title": "编码器", + "url": "/manual/2.0/core/codec.html#编码器", + "content": "编码器目前,仅包含消息构建器,您可以通过注解 @Message 将您环境中的错误码、错误消息的配置注入到 MessageObject 类型的属性中。我们在当前现代应用中,通常会在业务上定义业务错误信息。且我们返回给前端的可能是更模糊或通用的、人为可读性更强的错误信息,而且可能两种不同原因引起的错误,但是我们在返回给前端时的错误信息是一模一样的,这时我们就需要更精确的标识来定义错误,通常为错误码。此时,我们在应用中,通常会定义错误码和错误信息的映射关系。我们可用的方法大致是,第一代码里面写死;第二,定义错误枚举或者常量。无论哪种方式的都与代码高度耦合,可读性和可维护性都差。此时此刻,您可以通过 buession framework 的注解 @Message 来简化和解耦您的错误信息配置和代码。在传统 springmvc 应用中,您可以通过 context:property-placeholder 或者 util:properties 标签将错误信息 properties 配置文件加载到当前应用环境中。USER_NOT_FOUND.code = 10404USER_NOT_FOUND.message = 用户不存在\n\nUSER_LOGIN_FAILURE.code = 10405\nUSER_LOGIN_FAILURE.message = 登录失败\n\n\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(\"USER_NOT_FOUND\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n您可以自定义错误代码和错误消息的 key,默认分别为:code、message。此时您需要在注解 @Message 上显示指定错误代码和错误消息的 key。USER_NOT_FOUND.errorCode = 10404USER_NOT_FOUND.errorMessage = 用户不存在\n\nUSER_LOGIN_FAILURE.errorCode = 10405\nUSER_LOGIN_FAILURE.errorMessage = 登录失败\nimport com.buession.core.codec.Message;import com.buession.core.codec.MessageObject;\n\npublic UserServiceImpl implements UserService {\n\n\t@Message(value = \"USER_NOT_FOUND\", code = \"errorCode\", message = \"errorMessage\")\n\tprivate MessageObject userNotFound;\n\n\t@Override\n\tpublic User update(User user) throws Exception{\n\t\tUser dbUser = get(user.getId());\n\n\t\tif(dbUser == null){\n\t\t\tthrow new Exception(userNotFound.getMessage() + \"(code: \" + userNotFound.getCode() + \")\");\n\t\t\t// 用户不存在(code: 10404)\n\t\t}\n\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/codec.html#编码器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/collect.html", + "children": [ + { + "title": "收集器", + "url": "/manual/2.0/core/collect.html#收集器", + "content": "收集器数组、Map、集合的工具类" + }, + { + "title": "数组", + "url": "/manual/2.0/core/collect.html#收集器-数组", + "content": "数组数组工具类 Arrays 继承自 org.apache.commons.lang3.ArrayUtils 封装了元素是否存在检测、元素索引位置获取、拼接成字符串、转换为 List、Set 以及字符串类型的数组、数组合并、数组元素操作等方法。检测数组 array 中是否存在元素 element:import com.buession.core.collect.Arrays;\nboolean result = Arrays.contains(array, element);\n返回元素 element 在数组 array 中第一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.indexOf(array, element);\n返回元素 element 在数组 array 中最后一次出现的位置的索引,不存在返回 -1:import com.buession.core.collect.Arrays;\nint result = Arrays.lastIndexOf(array, element);\n将字符串拼接会字符串:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString result = Arrays.toString(array);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString glue = \"-\";\nString result = Arrays.toString(array, glue);\n// 1-2-3\n可以通过方法 toList、toSet 转换为 List 和 Set:import com.buession.core.collect.Arrays;import java.util.List;\nimport java.util.Set;\n\nint[] array = {1, 2, 3};\nList list = Arrays.toList(array);\nSet set = Arrays.toSet(array);\n将数组转换为字符串类型的数组:import com.buession.core.collect.Arrays;\nint[] array = {1, 2, 3};\nString[] result = Arrays.toStringArray(array);\n将数组进行合并:import com.buession.core.collect.Arrays;\nString[] result = Arrays.toStringArray(array1, array2, array3);\n对数组元素进行操作:import com.buession.core.collect.Arrays;\nString[] array = {\"A\", \"B\", \"C\"};\nString[] result = Arrays.map(array, String.class, fn);\n第二个参数为数组元素类型,第三个参数为 java.util.function.Function 的实现" + }, + { + "title": "Lists", + "url": "/manual/2.0/core/collect.html#收集器-lists", + "content": "ListsList 工具类 Lists 实现了将元素拼接成字符串、转换为 Set 操作。将字符串拼接会字符串:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString result = Lists.toString(list);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Lists.toString(list);\n// 1-2-3\n可以通过方法 toSet 将 List 转换为 Set:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nList list = ListBuilder.create().add(1).add(2).add(3).build();\nSet set = Lists.toSet(list);\n" + }, + { + "title": "Sets", + "url": "/manual/2.0/core/collect.html#收集器-sets", + "content": "SetsSett 工具类 Sets 实现了将元素拼接成字符串、转换为 List 操作。将字符串拼接会字符串:import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString result = Sets.toString(set);\n// 1, 2, 3\n可以通过参数 glue 指定连接符,默认为:\", \"import com.buession.core.collect.Sets;import com.buession.core.builder.SetBuilder;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nString glue = \"-\";\nString result = Sets.toString(list);\n// 1-2-3\n可以通过方法 toList 将 Set 转换为 List:import com.buession.core.collect.Lists;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\nimport java.util.Set;\n\nSet set = SetBuilder.create().add(1).add(2).add(3).build();\nList list = Sets.toList(set);\n" + }, + { + "title": "Maps", + "url": "/manual/2.0/core/collect.html#收集器-maps", + "content": "MapsMap 工具类 Maps 实现了对 key 和 value 进行操作,实现了将 value 转换为 List 和 Set。对 Map 进行操作:import com.buession.core.collect.Maps;import java.util.Map;\nimport java.util.HashMap;\n\nMap maps = new HashMap();\nMap result = Maps.map(maps, (key)->key, (value)->value == null ? null : value.toString());\n第二个、第三参数为 java.util.function.Function 的实现可以通过方法 toList 将 Map 的 value 转换为 List:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.List;\n\nList list = Maps.toList(maps);\n可以通过方法 toSet 将 Map 的 value 转换为 Set:import com.buession.core.collect.Maps;import com.buession.core.builder.ListBuilder;\nimport java.util.Set;\n\nSet set = Maps.toSet(maps);\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/collect.html#收集器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/context.html", + "children": [ + { + "title": "上下文", + "url": "/manual/2.0/core/context.html#上下文", + "content": "上下文注解 com.buession.core.context.stereotype.Manager 用于在分层应用中,标记当前类是一个 manager 类。类似于 org.springframework.stereotype.Service 加上该注解会将当前类自动注入到 spring 容器中,用法和 @Service 一样。在多层应用架构中 Manager 层通常为:通用业务处理层,处于 Dao 层之上,Service 层之下。主要特征如下:逻辑少\n与 Dao 层进行交互,多个 Dao 层的复用\nService 通用底层逻辑的下沉,如:缓存方案、中间件通用处理、以及对第三方平台封装的层\nimport com.buession.core.context.stereotype.Manager;import org.springframework.stereotype.Service;\n\npublic interface UserManager {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Manager\npublic class UserManagerImpl implements UserManager {\n\n\t@Autowired\n\tprivate UserDao userDao;\n\n\t@Autowired\n\tprivate UserProfileDao userProfileDao;\n\n\t@Autowired\n\tprivate RedisTemplate redisTemplate;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = redisTemplate.hGetObject(\"user\", Integer.toString(id), User.class);\n\n\t\tif(user == null){\n\t\t\tuser = userDao.getByPrimary(id);\n\t\t\tif(user != null){\n\t\t\t\tuser.setProfile(userProfileDao.getByUserId(id));\n\t\t\t\tredisTemplate.hSet(\"user\", Integer.toString(id), user);\n\t\t\t}else{\n\t\t\t\tthrow new RuntimeException(\"用户不存在\");\n\t\t\t}\n\t\t}\n\n\t\treturn user;\n\t}\n\n}\n\npublic interface UserService {\n\n\tUser getByPrimary(int id);\n\n}\n\n@Service\npublic class UserServiceImpl implements UserService {\n\n\t@Autowired\n\tprivate UserManager userManager;\n\n\n\t@Override\n\tpublic User getByPrimary(int id){\n\t\tUser user = userManager.getByPrimary(id);\n\n\t\t...\n\n\t\treturn user;\n\t}\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/context.html#上下文-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/converter.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.0/core/converter.html#构建器", + "content": "构建器数据转化器,基于 mapstruct 的 POJO 类映射接口定义。定义了常用的数据转化器。接口定义:@FunctionalInterfacepublic interface Converter {\n\n\tT convert(final S source);\n\n}\n将类似为 S 的对象转换为类型为 T 的对象。" + }, + { + "title": "内置转换器", + "url": "/manual/2.0/core/converter.html#构建器-内置转换器", + "content": "内置转换器\n\n转换器\n说明\n\n\n\n\nArrayConverter\n将 S 类型的数组转换为 T 类型的数组\n\n\nEnumConverter\n枚举转换器,将字符串转换为枚举 E\n\n\nBinaryEnumConverter\n枚举转换器,将 byte 数组转换为枚举 E\n\n\nBooleanStatusConverter\n将布尔值转换为 com.buession.lang.Status\n\n\nStatusBooleanConverter\n将 com.buession.lang.Status 转换为布尔值\n\n\nPredicateStatusConverter\n通过 java.util.function.Predicate 对某种数据类型的数据进行判断结果转换为 com.buession.lang.Status\n\n\nListConverter\n将 S 类型的 List 对象转换为 T 类型的 List 对象\n\n\nSetConverter\n将 S 类型的 Set 对象转换为 T 类型的 Set 对象\n\n\nMapConverter\n将 Key 为 SK 类型、值为 SV 类型的 Map 对象转换为 Key 为 TK 类型、值为 TV 类型的 Map\n\n\n将 key 为 Integer 类型,value 为 Object 类型的 Map 转换成 key 为 String 类型,value 为 String 类型的 Map 对象import com.buession.core.converter.MapConverter;import java.util.Map;\n\nMap source;\nMap target;\nMapConverter converter = new MapConverter();\n\ntarget = converter.convert(source);\n将字符串转换为枚举import com.buession.core.converter.EnumConverter;import com.buession.lang.Gender;\n\nGender target;\nEnumConverter converter = new EnumConverter(Gender.class);\n\ntarget = converter.convert(\"FEMALE\");\n// Gender.FEMALE\n\ntarget = converter.convert(\"F\");\n// null\n" + }, + { + "title": "POJO 类映射", + "url": "/manual/2.0/core/converter.html#构建器-pojo-类映射", + "content": "POJO 类映射我们通过 com.buession.core.converter.mapper.Mapper 接口约定了,基于 mapstruct POJO 类的映射接口规范。关于 mapstruct 的更多文章,可通过搜索引擎自行搜索。public interface Mapper {\n\t/**\n\t * 将源对象映射到目标对象\n\t *\n\t * @param object\n\t * \t\t源对象\n\t *\n\t * @return 目标对象实例\n\t */\n\tT mapping(S object);\n\n\t/**\n\t * 将源对象数组映射到目标对象数组\n\t *\n\t * @param object\n\t * \t\t源对象数组\n\t *\n\t * @return 目标对象实例数组\n\t */\n\tT[] mapping(S[] object);\n\n\t/**\n\t * 将源 list 对象映射到目标 list 对象\n\t *\n\t * @param object\n\t * \t\t源 list 对象\n\t *\n\t * @return 目标对象 list 实例\n\t */\n\tList mapping(List object);\n\n\t/**\n\t * 将源 set 对象映射到目标 set 对象\n\t *\n\t * @param object\n\t * \t\t源 set 对象\n\t *\n\t * @return 目标对象 set 实例\n\t */\n\tSet mapping(Set object);\n\n}\n我们还可以使用工具类 com.buession.core.converter.mapper.PropertyMapper 将值从提供的源映射到目标,此方法来拷贝并简单修改于:springboot 中的 org.springframework.boot.context.properties.PropertyMapper,和原生 springboot 中的用法一样。import com.buession.core.converter.mapper.PropertyMapper;\nUser source = new User();\nUser target = new User();\n\nPropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();\npropertyMapper.form(source::getId).to(target:setId)\n// null\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/converter.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/datetime.html", + "children": [ + { + "title": "日期时间", + "url": "/manual/2.0/core/datetime.html#日期时间", + "content": "日期时间日期、时间工具。目前,仅有少量的 API,参考 PHP 中的日期时间函数而来。获取当前 Unix 时间戳(秒):import com.buession.core.datetime.DateTime;\nDateTime.unixtime();\n该方法我们在实际业务中经常用到。以 \"msec sec\" 的格式返回当前 Unix 时间戳和微秒数:import com.buession.core.datetime.DateTime;\nDateTime.microtime();\n// 1657171717 948000\n该方法参考 PHP 的 microtime 函数而来。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/datetime.html#日期时间-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/id.html", + "children": [ + { + "title": "ID 生成器", + "url": "/manual/2.0/core/id.html#id-生成器", + "content": "ID 生成器基于 UUID、jnanoid ID、雪花算法等等的 ID 生成器。您可以通过 buession framework 提供的 ID 生成器,生成唯一 ID。接口规范。public interface IdGenerator {\n\t/**\n\t * 获取下一个 ID\n\t *\n\t * @return ID\n\t */\n\tT nextId();\n\n}\n" + }, + { + "title": "ID 生成器", + "url": "/manual/2.0/core/id.html#id-生成器-id-生成器", + "content": "ID 生成器\n\n生成器\n说明\n\n\n\n\nAtomicSimpleIdGenerator\n基于 AtomicLong 递增的,简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nAtomicUUIDIdGenerator\n基于 AtomicLong 递增的,UUID ID 生成器\n\n\nNanoIDIdGenerator\njnanoid ID 生成器,可通过构造函数指定字符范围、长度\n\n\nRandomDigitIdGenerator\n随机数 ID 生成器,返回指定范围内的随机数,默认为 1L ~ Long.MAX_VALUE,可通过构造函数指定\n\n\nRandomIdGenerator\n随机字符 ID 生成器,可通过构造函数指定生成长度,默认 16 位\n\n\nSimpleIdGenerator\n简单 ID 生成器,基于 UUID 生成,将 UUID 结果中的 \"-\" 过滤掉\n\n\nSnowflakeIdGenerator\n雪花算法 ID 生成器,此处不解决时钟回拨的问题,您可以通过构造函数指定开始时间、数据中心 ID、数据中心 ID 所占的位数等等值\n\n\nUUIDIdGenerator\nUUID ID 生成器\n\n\nimport com.buession.core.id.AtomicUUIDIdGenerator;import com.buession.core.id.NanoIDIdGenerator;\nimport com.buession.core.id.SnowflakeIdGenerator;\nimport com.buession.core.id.UUIDIdGenerator;\nimport com.buession.core.id.SimpleIdGenerator;\n\nAtomicUUIDIdGenerator idGenerator = new AtomicUUIDIdGenerator();\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000001\nidGenerator.nextId(); // 00000000-0000-0000-0000-000000000002\n\nNanoIDIdGenerator idGenerator = new NanoIDIdGenerator();\nidGenerator.nextId(); // omRdTPCug5z_Uk1E_x3ozu3Avyyl3XSK\n\nSnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator();\nidGenerator.nextId(); // 170602258864545792\n\nUUIDIdGenerator idGenerator = new UUIDIdGenerator();\nidGenerator.nextId(); // 8634a166-e7d6-4160-85eb-3433107de5a4\n\nSimpleIdGenerator idGenerator = new SimpleIdGenerator();\nidGenerator.nextId(); // 26d9ed57fdad443894b988eeabc69c05\n注:关于雪花算法、jnanoid 算法的可自行搜索。\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/id.html#id-生成器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/math.html", + "children": [ + { + "title": "数学函数", + "url": "/manual/2.0/core/math.html#数学函数", + "content": "数学函数定义了实用的数学函数。\n\n方法\n说明\n\n\n\n\ncontinuousSum\n计算两个数之间连续相加之和\n\n\nrangeValue\n获取合法范围内的值,如果 value 小于 min,则返回 min;如果 value 大于 max,则返回 max;否则返回 value 本身\n\n\nimport com.buession.core.math.Math;\nlong result = Math.continuousSum(1, 100);\n// 5050\nimport com.buession.core.math.Math;\nlong value = 3;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 4\nimport com.buession.core.math.Math;\nlong value = 5;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 5\nimport com.buession.core.math.Math;\nlong value = 11;\nlong min = 4;\nlong max = 10;\nlong result = Math.rangeValue(value, min, max);\n// 10\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/math.html#数学函数-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/serializer.html", + "children": [ + { + "title": "构建器", + "url": "/manual/2.0/core/serializer.html#构建器", + "content": "构建器对象的序列化和反序列化,包括二进制和 JSON。您可以通过该 API,实现将对象序列化成二进制或 JSON 字符串;或将二进制、JSON 字符串反序列化为对象。" + }, + { + "title": "序列化、反序列化类", + "url": "/manual/2.0/core/serializer.html#构建器-序列化、反序列化类", + "content": "序列化、反序列化类\n\n类\n说明\n\n\n\n\nDefaultByteArraySerializer\n将对象序列化为二进制,或将二进制反序列化为对象\n\n\nFastJsonJsonSerializer\n基于 FastJSON 的对象与 JSON 之间的序列化和反序列化\n\n\nGsonJsonSerializer\n基于 Gson 的对象与 JSON 之间的序列化和反序列化\n\n\nJacksonJsonSerializer\n基于 Jackson2 的对象与 JSON 之间的序列化和反序列化\n\n\n通用标准方法是,将对象序列化为字符串,或将字符串反序列化为对象\nDefaultByteArraySerializer 序列化成字符串,逻辑是在对象序列化为 byte 数组后,通过 URLEncoder.encode 进行编码;反序列化,则先通过 URLDecoder.decode 进行解码成 byte 数组,在反序列化为对象\nDefaultByteArraySerializer 支持对象与 byte 数组数组之间的序列化和反序列化\n在将 JSON 字符串反序列化为对象时,默认返回类型依据 fastjson、jackson 等原生逻辑\nFastJsonJsonSerializer、GsonJsonSerializer、JacksonJsonSerializer 可以通过参数 Class、TypeReference 指定返回的对象类型\ncom.buession.core.serializer.type.TypeReference 是某类型的一个指向或者引用,用于屏蔽 fastjson、gson、jackson 中通过 JDK Type 指定反序列化的类型;在 fastjson、gson 中是直接指定 Type,在 jackson 中是通过 com.fasterxml.jackson.core.type.TypeReference 类返回\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/serializer.html#构建器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/validator.html", + "children": [ + { + "title": "验证器", + "url": "/manual/2.0/core/validator.html#验证器", + "content": "验证器数据验证器及其注解。该 API 提供了大量通用的适用于本土化的常用验证方法,如:判断是否为 null、判断是否不为 null、判断(字符串、数组、集合、Map等)是否为空、判断(字符串、数组、集合、Map等)是否不为空、IP 地址合法性验证、ISBN 合法性验证、身份证号码验证、QQ 验证。并提供对应的基于 javax.validation 的校验注解。验证是否为 null判断任意对象是否为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNull(obj);\n验证是否不为 null判断任意对象是否不为 nullimport com.buession.core.validator.Validate;\nboolean result = Validate.isNotNull(obj);\n判断字符串是否为空白字符串判断字符串是否为空白字符串,为 null 或长度为 0 时也返回 true;其余情况,字符串中含有任意可打印字符串则为 falseimport com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\n\";\nboolean result = Validate.isBlank(str); // true\n\nString str = \"\\\\r\\\\na\";\nboolean result = Validate.isBlank(str); // false\n注:isNotBlank 与之相反\n判断是否为空isEmpty 可判断字符串、数组、集合、Map 是否为空,即:为 null 或长度/大小为 0 时,表示为空import com.buession.core.validator.Validate;\nString str = null;\nboolean result = Validate.isEmpty(str); // true\n\nString str = \" \";\nboolean result = Validate.isEmpty(str); // false\n\nboolean result = Validate.isEmpty(new String[]{}); // true\n\nboolean result = Validate.isEmpty(new Integer[]{1}); // false\n注:isNotEmpty 与之相反\n判断是否在两个数之间isBetween 可判断一个整型或浮点型,是否在两个整型或者浮点型数字之间import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3); // true\n\nboolean result = Validate.isBetween(2, 2, 3); // false\n可通过参数设置是否包含边界值import com.buession.core.validator.Validate;\nboolean result = Validate.isBetween(2, 1, 3, true); // true\n\nboolean result = Validate.isBetween(2, 2, 3, true); // true\n判断是否为电话号码isTel 可判断一个字符串是否为电话号码,仅支持普通座机号码,不支持 110、400、800等特殊号码。格式,需符合:86-区号-电话号码、区号-电话号码、(区号)电话号码、(区号)电话号码、电话号码。可通过参数 com.buession.core.validator.routines.TelValidator.AreaCodeType 指定区号的控制策略。仅支持中国的电话号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isTel(\"028-12345678\"); // true\n\nboolean result = Validate.isTel(\"028-02345678\"); // false\n判断是否为手机号码isMobile 可判断一个字符串是否为手机号码。仅支持中国的手机号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isMobile(\"028-12345678\"); // false\n\nboolean result = Validate.isMobile(\"13800138000\"); // true\n判断是否为邮政编码isPostCode 可判断一个字符串是否为邮政编码。仅支持中国的邮政编码。import com.buession.core.validator.Validate;\nboolean result = Validate.isPostCode(\"043104\"); // false\n\nboolean result = Validate.isPostCode(\"643104\"); // true\n判断是否为 QQ 号码isQQ 可判断一个字符串是否为 QQ 号码。import com.buession.core.validator.Validate;\nboolean result = Validate.isQQ(\"043104\"); // false\n\nboolean result = Validate.isQQ(\"251329041\"); // true\n判断是否为身份证号码isIDCard 可判断一个字符串是否合法的身份证号码。仅支持 18 位的身份证号码。身份证号码需符合其身份证号码编排规律。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\");\n可以和身份证号码进行比对,其实该方法的实际作用不是很大,只是在业务系统里面有需要填写身份证号码和出生日期时,简化验证出生日期和身份证号码中的出生日期是否一致。import com.buession.core.validator.Validate;\nboolean result = Validate.isIDCard(\"xxxxxxxxxxxxxxxxx\", true, \"2000-01-01\");\n其它,更多方法可以参考手册。" + }, + { + "title": "注解", + "url": "/manual/2.0/core/validator.html#验证器-注解", + "content": "注解javax 的 validation 是 Java 定义的一套基于注解的数据校验规范,buession framework 基于 javax.validation 实现了 Validate 中所有验证方法的校验注解。\n\n注解\n验证的数据类型\n说明\n\n\n\n\n@Alnum\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Alpha\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Numeric\nCharSequence 的子类型,Character\n验证注解的元素值是否为数字\n\n\n@Between\nshort、int、double 等任何 Number 的子类型\n验证注解的元素值是否为在两个数之间\n\n\n@Empty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否为空\n\n\n@NotEmpty\nCharSequence、Map、Collection、Iterator、Enumeration 的子类型和数组\n验证注解的元素值是否不为空\n\n\n@HasText\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@IDCard\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Ip\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Isbn\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@MimeType\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Mobile\nCharSequence 的子类型\n验证注解的元素值是否有非空字符\n\n\n@Null\n任意类型\n验证注解的元素值是否为 null\n\n\n@NotNull\n任意类型\n验证注解的元素值是否为 null\n\n\n@Port\nInteger\n验证注解的元素值是否为 null\n\n\n@PostCode\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@QQ\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Tel\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n@Xdigit\nCharSequence 的子类型\n验证注解的元素值是否为 null\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/validator.html#验证器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/utils.html", + "children": [ + { + "title": "工具类", + "url": "/manual/2.0/core/utils.html#工具类", + "content": "工具类常用通用性工具类。" + }, + { + "title": "Byte 数组比较", + "url": "/manual/2.0/core/utils.html#工具类-byte-数组比较", + "content": "Byte 数组比较ByteArrayComparable 类为 java Comparable 的实现,实现了 byte 数组的比较。" + }, + { + "title": "注解工具", + "url": "/manual/2.0/core/utils.html#工具类-注解工具", + "content": "注解工具AnnotationUtils 继承 org.springframework.core.annotation.AnnotationUtils ,在此基础上新增了方法 hasClassAnnotationPresent(final Class clazz, final Class[] annotations)、hasMethodAnnotationPresent(Method method, final Class[] annotations) 判断类或者方法上是否有待检测的注解中的其中一个注解。" + }, + { + "title": "断言", + "url": "/manual/2.0/core/utils.html#工具类-断言", + "content": "断言Assert 和 springframework 中的注解类似,不过逻辑有些相反。" + }, + { + "title": "Byte 工具", + "url": "/manual/2.0/core/utils.html#工具类-byte-工具", + "content": "Byte 工具ByteUtils 可以将 byte 数组转换为 char 或者 char 数组。import com.buession.core.utils.ByteUtils;\nbyte[] bytes;\nchar c = ByteUtils.toChar(bytes);\n\nchar[] chars = ByteUtils.toChar(bytes);\nbyte 数组连接。import com.buession.core.utils.ByteUtils;\nbyte[] dest;\nbyte[] source\nbyte[] result = ByteUtils.concat(dest, source);\n" + }, + { + "title": "Character 工具", + "url": "/manual/2.0/core/utils.html#工具类-character-工具", + "content": "Character 工具CharacterUtils 继承 org.apache.commons.lang3.CharUtils,可以将 char、char 数组转换为 byte 数组。import com.buession.core.utils.CharacterUtils;\nchar c;\nbyte[] bytes = ByteUtils.toBytes(c);\n\nchar[] chars;\nbyte[] bytes = ByteUtils.toBytes(chars);\n" + }, + { + "title": "数字工具", + "url": "/manual/2.0/core/utils.html#工具类-数字工具", + "content": "数字工具NumberUtils 提供了对数字相关的操作。\n\n方法\n说明\n\n\n\n\nint2bytes\n将 int 转换为 byte[]\n\n\nbytes2int\n将 byte[] 转换为 int\n\n\nlong2bytes\n将 long 转换为 byte[]\n\n\nbytes2long\n将 byte[] 转换为 long\n\n\nfloat2bytes\n将 float 转换为 byte[]\n\n\nbytes2float\n将 byte[] 转换为 float\n\n\ndouble2bytes\n将 double 转换为 byte[]\n\n\nbytes2double\n将 byte[] 转换为 double\n\n\n" + }, + { + "title": "字符串工具", + "url": "/manual/2.0/core/utils.html#工具类-字符串工具", + "content": "字符串工具StringUtils 继承了 org.apache.commons.lang3.StringUtils,封装了多字符串更多的操作。截取字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.substr(\"abcde\", 1); // bcde\nString result = StringUtils.substr(\"abcde\", 1, 2); // bc\n生成随机字符串import com.buession.core.utils.StringUtils;\nString result = StringUtils.random(length);\n比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 3); // true\nboolean result = StringUtils.equals(\"abcd\", \"abce\", 4); // false\n忽略大小写比较两个 CharSequence 前 length 位是否相等import com.buession.core.utils.StringUtils;\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"Abce\", 3); // true\nboolean result = StringUtils.equalsIgnoreCase(\"abcd\", \"aBce\", 4); // false\n" + }, + { + "title": "拼音工具", + "url": "/manual/2.0/core/utils.html#工具类-拼音工具", + "content": "拼音工具PinyinUtils 封装了获取中文拼音、拼音首字母的方法。import com.buession.core.utils.PinyinUtils;\nString result = PinyinUtils.getPinyin(\"中国\"); // zhongguo\nString result = PinyinUtils.getPinYinFirstChar(\"中国\"); // zg\n" + }, + { + "title": "随机数工具", + "url": "/manual/2.0/core/utils.html#工具类-随机数工具", + "content": "随机数工具RandomUtils 封装了随机数的生成。\n\n方法\n说明\n\n\n\n\nnextBoolean\n随机布尔值\n\n\nnextBytes\n随机字节数组\n\n\nnextInt\n生成指定范围内的 int 值,默认:0 到 Integer.MAX_VALUE\n\n\nnextLong\n生成指定范围内的 long 值,默认:0 到 Long.MAX_VALUE\n\n\nnextFloat\n生成指定范围内的 float 值,默认:0 到 Float.MAX_VALUE\n\n\nnextDouble\n生成指定范围内的 double 值,默认:0 到 Double.MAX_VALUE\n\n\n" + }, + { + "title": "Properties 工具", + "url": "/manual/2.0/core/utils.html#工具类-properties-工具", + "content": "Properties 工具PropertiesUtils 封装了对 Properties 的操作,主要是 Properties.getProperty 的值为字符串,而我们在实际场景中,很多时候其值可能为 int、double。传统方法是,我们在代码中通过 Properties.getProperty 获取其值后,对其进行转换;而 PropertiesUtils 简化了操作。import com.buession.core.utils.SystemPropertyUtils;\nInteger result = PropertiesUtils.getInteger(properties, key);\nBoolean result = PropertiesUtils.getBoolean(properties, key);\n" + }, + { + "title": "System Property 工具", + "url": "/manual/2.0/core/utils.html#工具类-system-property-工具", + "content": "System Property 工具SystemPropertyUtils 封装了系统属性或系统环境变量的操作。设置属性方法 setProperty 对 System.setProperty 的封装,唯一区别是:SystemPropertyUtils.setProperty 的值可以为非字符串,该方法类会将非字符串值转换为字符串再调用 System.setProperty。import com.buession.core.utils.SystemPropertyUtils;\nSystemPropertyUtils.setProperty(\"http.port\", 8080);\nSystemPropertyUtils.setProperty(\"http.ssl.enable\", false);\n获取属性方法 getProperty 会先通过 System.getProperty 进行获取,若未获取到值,再调用 System.getenv 进行获取。String value = System.getProperty(name);\nif(Validate.hasText(value) == false){\n value = System.getenv(name);\n}\n" + }, + { + "title": "版本工具", + "url": "/manual/2.0/core/utils.html#工具类-版本工具", + "content": "版本工具VersionUtils 提供了对两个版本值的比较方法和获取类、jar 对应的版本。import com.buession.core.utils.VersionUtils;\nVersionUtils.compare(\"1.0.0\", \"1.0.1-beta\"); // -1\nVersionUtils.compare(\"1.0.0\", \"1.0.0r\"); // -1\n规则:dev < alpha = a < beta = b < rc < release < pl = p < 纯数字版本获取类的版本值import com.buession.core.utils.VersionUtils;\nByteUtils.determineClassVersion(com.buession.core.utils.VersionUtils.class); // 2.0.0\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/utils.html#工具类-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/other.html", + "children": [ + { + "title": "其它", + "url": "/manual/2.0/core/other.html#其它", + "content": "其它通用的接口定义,框架自身类,以及其它杂项。" + }, + { + "title": "框架自身工具", + "url": "/manual/2.0/core/other.html#其它-框架自身工具", + "content": "框架自身工具获取 Buession Framework 版本:import com.buession.core.Framework;import com.buession.core.BuesssionFrameworkVersion;\n\nBuesssionFrameworkVersion.getVersion(); // 2.0.0\nFramework.VERSION; // 2.0.0\n获取 Buession Framework 框架名称:import com.buession.core.Framework;\nFramework.NAME; // \"Buession\"\n" + }, + { + "title": "命令执行器", + "url": "/manual/2.0/core/other.html#其它-命令执行器", + "content": "命令执行器命令执行器接口:/** * 命令执行器\n *\n * @param \n * \t\t命令上下文\n * @param \n * \t\t命令执行返回值\n */\n@FunctionalInterface\npublic interface Executor {\n\n\t/**\n\t * 命令执行\n\t *\n\t * @param context\n\t * \t\t命令执行器上下文\n\t *\n\t * @return 命令执行返回值,R 类型的实例\n\t */\n\tR execute(C context);\n\n}\n您可以通过,该接口执行一个命令上下文的方法,并返回一个值。该方法有些类似代理模式的代理类。" + }, + { + "title": "销毁接口", + "url": "/manual/2.0/core/other.html#其它-销毁接口", + "content": "销毁接口功能类似 java.io.Closeable。public interface Destroyable {\n\t/**\n\t * 销毁相关资源\n\t *\n\t * @throws IOException\n\t * \t\tIO 错误时抛出\n\t */\n\tvoid destroy() throws IOException;\n\n}\n" + }, + { + "title": "Rawable", + "url": "/manual/2.0/core/other.html#其它-rawable", + "content": "Rawable原始的,约定实现该接口的类,必须返回原始字节数组。public interface Rawable {\n\t/**\n\t * 返回原始的字节数组\n\t *\n\t * @return 原始的字节数组\n\t */\n\tbyte[] getRaw();\n\n}\n" + }, + { + "title": "名称节点", + "url": "/manual/2.0/core/other.html#其它-名称节点", + "content": "名称节点名称节点,约定实现该接口的类应该返回一个名称public interface NamedNode {\n\t/**\n\t * 返回节点名称\n\t *\n\t * @return 节点名称\n\t */\n\t@Nullable\n\tString getName();\n\n}\n" + }, + { + "title": "分页", + "url": "/manual/2.0/core/other.html#其它-分页", + "content": "分页com.buession.core.Pagination 为一个分页 POJO 类,包括了当前页码、每页大小、上一页、下一页、总页数、总记录数、数据列表。能够根据设定的其中一个值,计算另外的值。" + } + ] + }, + { + "title": "buession-core 参考手册", + "content": "", + "url": "/manual/2.0/core/exception.html", + "children": [ + { + "title": "异常", + "url": "/manual/2.0/core/exception.html#异常", + "content": "异常通用异常的定义。\n\n异常\n说明\n\n\n\n\nAccessException\n拒绝访问异常\n\n\nClassInstantiationException\n类实例化异常\n\n\nConversionException\n数据类型转换异常\n\n\nDataAlreadyExistException\n数据已存在异常\n\n\nDataNotFoundException\n数据不存在或未找到异常\n\n\nInsteadException\n类方法废弃后,需要使用其它类库方法来替代\n\n\nNestedRuntimeException\n嵌套运行时异常\n\n\nOperationException\n运算异常\n\n\nPresentException\n--\n\n\nSerializationException\n序列化异常\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/core/exception.html#异常-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-cron 参考手册", + "content": "对 quartz 的二次封装", + "url": "/manual/2.0/cron/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/cron/index.html#安装", + "content": "安装 com.buession\n buession-cron\n x.x.x\n\n由于在过去的工作中,定时任务基本使用 quartz 来实现;但是在初始化定时任务项目时,大量基本相同的代码,因此对此部分做了二次封装,简化了 quartz 项目的初始化。由于在现在有众多优秀的分布式定时任务,如:elastic-job、xxl-job 等等,因此直接使用 quartz 应该会越来越少(个人主观猜测),即使使用 quartz 初始化也简单,故该模块将不做说明。且在今后的版本中,该模块可能会被废弃。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/cron/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "对 mybatis、spring-data-mongo 常用方法(如:根据条件获取单条记录、根据主键获取单条记录、分页、根据条件删除数据、根据主键删除数据)进行了二次封装。从代码层面实现了数据库的读写分离,insert、update、delete 操作主库,select 操作从库。", + "url": "/manual/2.0/dao/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/dao/index.html#安装", + "content": "安装 com.buession\n buession-dao\n x.x.x\n\n我们咋众多项目中,基本有常见的重复的对数据库的 CURD 操作,比如:根据主键查询数据、根据主键删除数据、获取一条记录。MyBatis 是一款优秀的持久层框架,应用广泛。MongoDB 是一款优秀的文档数据库。我自己根据从业多年的经验,从实际场合出发,将在业务层对数据库的常用操作进行了封装。对关系型数据库基于 MyBatis 二次封装,对 MongoDB 基于 spring-data-mongodb;在未来也许会考虑,增加 jpa 和 JdbcTemplate 对关系型数据库的二次封装。同时,我们在代码层面实现了数据库的读写分离。我们没有改变 MyBatis 和 spring-data-mongodb 的任何底层逻辑,本质就是 MyBaits 和 spring-data-mongodb;我们唯一做了的就是,定义和是了大家在应用程序中常用的方法,让您不在重复去编写该部分代码;以及在代码层面实现了数据的读写分离。" + }, + { + "title": "Dao 接口", + "url": "/manual/2.0/dao/index.html#dao-接口", + "content": "Dao 接口接口定义,可见:https://javadoc.io/static/com.buession/buession-dao/2.0.2/com/buession/dao/Dao.htmlpublic interface Dao {}\nP:主键类型\nE:实体类\n分页对象 com.buession.dao.Pagination 继承自 com.buession.core.Pagination,增加了偏移量属性 offset。条件为 Map 类型,允许为 null。排序为 Map 类型,允许为 null。MyBatisBuession Framework 扩展 MyBatis 的文档。MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/dao/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.0/dao/mybatis.html", + "children": [ + { + "title": "MyBatis", + "url": "/manual/2.0/dao/mybatis.html#mybatis", + "content": "MyBatisBuession Framework 扩展 MyBatis 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.0/dao/mybatis.html#mybatis-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMyBatisDao;且存在 bean 名为 masterSqlSessionTemplate、slaveSqlSessionTemplates 的 bean 实例。masterSqlSessionTemplate 操作主库,实现插入、更新、删除操作;slaveSqlSessionTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveSqlSessionTemplate() 在所有的 slave templates 中随机返回一个 slave SqlSessionTemplate bean 实例。当然,您也可以通过 getSlaveSqlSessionTemplate(final int index) 指定索引的 slave SqlSessionTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave SqlSessionTemplate bean 实例列表,将会返回 master SqlSessionTemplate bean 实例,buession framework 屏蔽了这些技术细节。" + }, + { + "title": "Mybatis 约定", + "url": "/manual/2.0/dao/mybatis.html#mybatis-mybatis-约定", + "content": "Mybatis 约定如果集成 AbstractMyBatisDao 类,必须重写方法 getStatement(),通过此方法返回每个 Mapper namespace\nnamespace com.buession.dao.test.dao;\npublic class UserDaoImpl extends AbstractMyBatisDao {\n\n\t@Override\n\tprotected String getStatement(){\n\t\treturn \"com.buession.dao.test.dao.UserMapper\";\n\t}\n\n}\n\n\nMapper 的 SQL ID 和方法名保持一致\n\n\nSQL ID\n说明\n返回值\n\n\n\n\ninsert\n插入数据\n影响的行数\n\n\nbatchInsert\n批量插入数据,默认循环插入;您可以重写该方法实现 SQL 批量插入\n每次插入影响的行数列表\n\n\nreplace\n替换数据,即:REPLACE 语句\n影响的行数\n\n\nbatchReplace\n批量替换数据,即:REPLACE 语句\n每次替换数据影响的行数列表\n\n\nupdate\n更新数据\n更新条数\n\n\nupdateByPrimary\n根据主键更新数据,注:主键参数值是会覆盖实体类主键参数对应的类属性的值\n更新条数\n\n\ngetByPrimary\n根据主键查询数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselectOne\n(根据条件)获取一条数据,SQL 层面需要保证最多只会返回一条数据\n一条数据结果\n\n\nselect\n查询数据\n数据结果列表\n\n\ngetAll\n查询所有数据\n数据结果列表\n\n\ncount\n获取记录数\n记录数\n\n\ndeleteByPrimary\n根据主键删除数据\n影响条数\n\n\ndelete\n删除数据\n影响条数\n\n\nclear\n清除数据\n影响条数\n\n\ntruncate\n截断数据\n影响条数\n\n\n注:要实现分页,必须实现 count,且和 select 的查询条件必须一致。因为,在分页方法中,首先会执行 count ,查询指定条件的记录数;如果记录数大于 0 时,才会执行 select 查询数据。在后续的开发中,我们将会使用拦截器实现。\n以上 SQL ID,只是一种约定,具体会呈现一种什么样的效果,还是完全屈居于您的 SQL 语句。\n" + }, + { + "title": "Mybatis 类型处理器", + "url": "/manual/2.0/dao/mybatis.html#mybatis-mybatis-类型处理器", + "content": "Mybatis 类型处理器MyBatis 自身提供大量优秀的类型处理器 TypeHandler,但任然不足。我们在此基础上扩展了一些 TypeHandler。名称空间为 org.apache.ibatis.type,不是 com.buession.dao。\n\nTypeHandler\n说明\n\n\n\n\nDefaultEnumTypeHandler\n默认 Enum 类型处理器,将值直接转换为枚举字段\n\n\nIgnoreCaseEnumTypeHandler\n忽略大小写 Enum 类型处理器,将值忽略大小写转换为枚举字段\n\n\nDefaultJsonTypeHandler\nJSON 处理器,将 JSON 格式的字符串值和类型 进行转换\n\n\nDefaultSetEnumTypeHandler\n默认 Enum 型 Set 类型处理器,将值直接转换为枚举字段作为 Set 的元素\n\n\nIgnoreCaseSetEnumTypeHandler\n忽略大小写 Enum 型 Set 类型处理器,将值忽略大小写转换为枚举字段作为 Set 的元素\n\n\nDefaultSetTypeHandler\n默认 Set 类型处理器,将值以 \",\" 拆分转换为 Set\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/dao/mybatis.html#mybatis-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-dao 参考手册", + "content": "", + "url": "/manual/2.0/dao/mongodb.html", + "children": [ + { + "title": "MongoDB", + "url": "/manual/2.0/dao/mongodb.html#mongodb", + "content": "MongoDBBuession Framework 扩展 spring-data-mongodb 的文档。" + }, + { + "title": "读写分离", + "url": "/manual/2.0/dao/mongodb.html#mongodb-读写分离", + "content": "读写分离要从代码层面实现读写分离,必须继承 AbstractMongoDBDao;且存在 bean 名为 masterMongoTemplate、slaveMongoTemplates 的 bean 实例。masterMongoTemplate 操作主库,实现插入、更新、删除操作;slaveMongoTemplates 操作从库,实现查询操作。默认查询操作,会通过方法 getSlaveMongoTemplate() 在所有的 slave templates 中随机返回一个 MongoTemplate bean 实例。当然,您也可以通过 getSlaveMongoTemplate(final int index) 指定索引的 slave MongoTemplate bean 实例(当然,我们不建议您这么做)。如果没有指定 slave MongoTemplate bean 实例列表,将会返回 master MongoTemplate bean 实例,buession framework 屏蔽了这些技术细节。AbstractMongoDBDao 的 replace 执行的也是 insert。在对 MongoDB 的操作条件中 value 可以为 com.buession.dao.MongoOperation,可以通过该类的方法控制条件等于值、大于值、小于值、LIKE值 等等运算。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/dao/mongodb.html#mongodb-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-geoip 参考手册", + "content": "对 com.maxmind.geoip2:geoip2 进行二次封装,实现支持根据 IP 地址获取所属 ISP、所属国家、所属城市等等信息。", + "url": "/manual/2.0/geoip/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/geoip/index.html#安装", + "content": "安装 com.buession\n buession-geoip\n x.x.x\n\n通常我们在应用中对用户注册、登录、以及其它操作记录 IP,我们更希望知道用户在什么城市进行的操作,如:微信公众号的内容发表于、微博的发布于等等,对于用户行为的安全审计等等有着极高的作用。geoip 在基于 maxmind geoip2 的基础上进行了二次封装,可以根据 IP(字符串形式的 IP,如:114.114.114.114、2001:0DB8:0000:0023:0008:0800:200C:417A ,IPV4 的数字表示:3739974408,java InetAddress)获取其 IP 地址的国家信息、城市信息、位置信息。" + }, + { + "title": "获取国家信息", + "url": "/manual/2.0/geoip/index.html#获取国家信息", + "content": "获取国家信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nCountry country = resolver.country(\"114.114.114.114\");\n// Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}\n\nCountry country = resolver.country(3739974408L); // 3739974408L => 222.235.123.8\n// Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}\n" + }, + { + "title": "获取城市信息", + "url": "/manual/2.0/geoip/index.html#获取城市信息", + "content": "获取城市信息import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nDistrict district = resolver.district(\"114.114.114.114\");\n// District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}\n\nDistrict district = resolver.district(3739974408L); // 3739974408L => 222.235.123.8\n// District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}\n" + }, + { + "title": "获取位置信息", + "url": "/manual/2.0/geoip/index.html#获取位置信息", + "content": "获取位置信息位置信息中包括了该 IP 比较全面的信息,包括:城市信息、国家信息、洲信息、经纬度、机构信息、时区等。import com.buession.geoip.model.Country;import com.buession.geoip.model.DatabaseResolver;\n\nDatabaseResolver resolver = new DatabaseResolver(DatabaseResolver.class.getResourceAsStream(\"/maxmind/City.mmdb\"));\nLocation location = resolver.location(\"114.114.114.114\");\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1814991, confidence=null, code='CN', originalName='China', name='中国', fullName='中华人民共和国', isInEuropeanUnion=false}, district=District{geoNameId=1799962, code=null, originalName='Nanjing', name='南京', fullName='江苏省南京', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省江苏省', postal=null, parent=District{geoNameId=1806260, code=null, originalName='Jiangsu', name='江苏省', fullName='江苏省', postal=null, parent=null}}}, traits=Traits{ipAddress='114.114.114.114', domain='null', isp='null', network=114.114.0.0/16, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=32.0617, longitude=118.7778, accuracyRadius=50}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Shanghai\",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null]}\n\nLocation location = resolver.location(3739974408L); // 3739974408L => 222.235.123.8\n// Location{continent=Continent{geoNameId=6255147, code='AS', originalName='Asia', name='亚洲'}, country=Country{geoNameId=1835841, confidence=null, code='KR', originalName='Republic of Korea', name='大韩民国', fullName='大韩民国', isInEuropeanUnion=false}, district=District{geoNameId=1835848, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=Postal{code='null', confidence=null}, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市首尔特别市', postal=null, parent=District{geoNameId=1835847, code=null, originalName='Seoul', name='首尔特别市', fullName='首尔特别市', postal=null, parent=null}}}, traits=Traits{ipAddress='222.235.123.8', domain='null', isp='null', network=222.235.120.0/21, connectionType=null, organization=null, autonomousSystemOrganization=null, autonomousSystemNumber=null, isAnonymous=false, isAnonymousProxy=false, isAnonymousVpn=false, isHostingProvider=false, isLegitimateProxy=false, isPublicProxy=false, isSatelliteProvider=false, isTorExitNode=false, userType='false', userCount=null, staticIpScore=null}, geo=Geo{latitude=37.5111, longitude=126.9743, accuracyRadius=200}, timeZone=sun.util.calendar.ZoneInfo[id=\"Asia/Seoul\",offset=32400000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null]}\n" + }, + { + "title": "缓存", + "url": "/manual/2.0/geoip/index.html#缓存", + "content": "缓存为了提高数据的处理能力,可以将获取过的数据缓存起来,下次获取同一 IP 信息时,可以直接从缓存中返回。您可以通过 DatabaseResolver 构造函数中的参数 cache 设置为 com.maxmind.db.NodeCache 的实现类即可,或直接使用类 CacheDatabaseResolver解析。我们默认使用 maxmind 内置的 CHMCache 来实现缓存,它是基于 ConcurrentHashMap 的内存缓存。" + }, + { + "title": "Resolver 的 Spring Factory Bean", + "url": "/manual/2.0/geoip/index.html#resolver-的-spring-factory-bean", + "content": "Resolver 的 Spring Factory Bean我们内置了 geoip 的 Resolver spring factory bean 类 GeoIPResolverFactoryBean,您可以通过它在您的 spring 项目中,初始化 Resolver 的实现类为 spring bean 对象。dbPath 和 stream 二选一即可,一个是指定 IP 的文件路径,一个是指定已加载的 IP 库的文件流。都不设置的默认以流的形式加载 buession-geoip 中的 IP 库文件。\nenableCache 可以控制是否缓存。\n" + }, + { + "title": "关于 IP 库", + "url": "/manual/2.0/geoip/index.html#关于-ip-库", + "content": "关于 IP 库buession-geoip 中包含了 maxmind 免费的 IP 所属城市和国家的库。由于在 jar 包中该数据库无法做到及时更新,在实际应用中,我们建议您从 maxmind 官网下载 IP 方法您的应用中,通过 DatabaseResolver 的构造函数指定 IP 库路径,这么做的好处是:在您的应用程序中,可以去保证 IP 库是更新的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/geoip/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "对 apache httpcomponents、okhttp3 进行封装,屏蔽了 apache httpcomponents 和 okhttp3 的不同技术细节,屏蔽了对 post form、post json 等等的技术细节。", + "url": "/manual/2.0/httpclient/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/httpclient/index.html#安装", + "content": "安装 com.buession\n buession-httpclient\n x.x.x\n\n我们在应用中使用 Http Client 功能时,经常因为从 apache httpcomponents 切换为 okhttp3,或者从 okhttp3 切换为 apache httpcomponents,需要改动大量的代码而烦恼。而当您使用了 buession-httpclient 时,该类库为您解决了这些烦恼,通过顶层设计,屏蔽了 apache httpcomponents 和 okhttp3 的细节,当您需要从一个 http 库更换为另外一个 http 库时,您只需要在 pom.xml 中引用不同的包,修改一下 httpclient 的初始化类和连接管理器即可。传统的方式: org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\nimport org.apache.http.HttpResponse;import org.apache.http.conn.HttpClientConnectionManager;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.client.methods.HttpPost;\n\nHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(new HttpClientConnectionManager()).build();\n\nHttpResponse response = httpClient.execute(new HttpPost(\"https://www.buession.com/\"));\n或者 com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport okhttp3.HttpClientConnectionManager;import okhttp3.OkHttpClient;\nimport okhttp3.ConnectionPool;\nimport okhttp3.Request;\nimport okhttp3.Request.Builder;\nimport okhttp3.Response;\n\nOkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());\nHttpClient httpClient = builder.build();\n\nBuilder requestBuilder = new Builder().post();\nrequestBuilder.url(\"https://www.buession.com/\");\nRequest okHttpRequest = requestBuilder.build();\n\nResponse httpResponse = httpClient.newCall(okHttpRequest).execute();\n现在,您只需要通过 buession-httpclient,即可屏蔽其中的细节。 com.buession\n buession-httpclient\n x.x.x\n\n\n org.apache.httpcomponents\n httpcore\n x.x.x\n\n\n org.apache.httpcomponents\n httpclient\n x.x.x\n\n或者 com.buession\n buession-httpclient\n x.x.x\n\n\n com.squareup.okhttp3\n okhttp\n x.x.x\n\nimport com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.OkHttpHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.conn.OkHttpClientConnectionManager;\nimport com.buession.httpclient.core.Response;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager()); // 或者 new OkHttpHttpClient(new OkHttpClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\n" + }, + { + "title": "展望", + "url": "/manual/2.0/httpclient/index.html#展望", + "content": "展望目前,buession-httpclient 仅支持同步,不支持异步。我们会在下一个小版本(即:2.1) 中,集成 apache httpcomponents 切换为 okhttp3 的异步 http 请求。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/httpclient/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.0/httpclient/configuration.html", + "children": [ + { + "title": "连接配置", + "url": "/manual/2.0/httpclient/configuration.html#连接配置", + "content": "连接配置您可以通过连接配置类 Configuration 配置 apache httpcomponents 和 okhttp3 的链接配置属性,buession-httpclient 内部会自动将 Configuration 的配置信息,转换为 apache httpcomponents 或 okhttp3 的配置信息。" + }, + { + "title": "配置属性说明", + "url": "/manual/2.0/httpclient/configuration.html#连接配置-配置属性说明", + "content": "配置属性说明\n\n属性名称\n数据类型\napache httpcomponents 对应配置\nokhttp3 对应配置\n默认值\n说明\n\n\n\n\nmaxConnections\nint\nmaxTotal\nmaxIdleConnections\n5000\n最大连接数\n\n\nmaxPerRoute\nint\ndefaultMaxPerRoute\n--\n500\n每个路由的最大连接数\n\n\nidleConnectionTime\nint\ncloseIdleConnections\nkeepAliveDuration\n60000\n空闲连接存活时长(单位:毫秒)\n\n\nconnectTimeout\nint\nconnectTimeout\nconnectTimeout\n3000\n连接超时时间(单位:毫秒)\n\n\nconnectionRequestTimeout\nint\nconnectionRequestTimeout\n--\n5000\n从连接池获取连接的超时时间(单位:毫秒)\n\n\nreadTimeout\nint\nsocketTimeout\nreadTimeout\n5000\n读取超时时间(单位:毫秒)\n\n\nallowRedirects\nBoolean\nredirectsEnabled\nfollowRedirects\n--\n是否允许重定向\n\n\nrelativeRedirectsAllowed\nBoolean\nrelativeRedirectsAllowed\n--\n--\n是否应拒绝相对重定向\n\n\ncircularRedirectsAllowed\nBoolean\ncircularRedirectsAllowed\n--\n--\n是否允许循环重定向\n\n\nmaxRedirects\nInteger\nmaxRedirects\n--\n--\n最大允许重定向次数\n\n\nauthenticationEnabled\nboolean\nauthenticationEnabled\n--\n--\n是否开启 Http Basic 认证\n\n\ncontentCompressionEnabled\nboolean\ncontentCompressionEnabled\n--\n--\n是否启用内容压缩\n\n\nnormalizeUri\nboolean\nnormalizeUri\n--\n--\n是否标准化 URI\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/httpclient/configuration.html#连接配置-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.0/httpclient/connectionmanager.html", + "children": [ + { + "title": "连接管理器", + "url": "/manual/2.0/httpclient/connectionmanager.html#连接管理器", + "content": "连接管理器连接管理器是用来管理 HTTP 连接的,包括设置连接配置、控制连接池。关于更多的连接池,可以参考 apache httpcomponents 和 okhttp3 的文档。您可以通过无参数的构造函数来创建连接管理器,也可以通过构造函数参数仅为 com.buession.httpclient.core.Configuration 来创建连接管理器,也可以构造函数通过 apache httpcomponents 或 okhttp3 原生的连接管理器类创建(此时,Configuration 的配置不任何意义,他不会修改您通过原生连接管理器实例中的参数配置)。" + }, + { + "title": "关于 okhttp 连接管理器", + "url": "/manual/2.0/httpclient/connectionmanager.html#连接管理器-关于-okhttp-连接管理器", + "content": "关于 okhttp 连接管理器okhttp3 本身是没有类似 apache httpcomponents 的链接管理器 org.apache.http.conn.HttpClientConnectionManager 的,我们为了在 buession-httpclient 的链接管理器实现 com.buession.httpclient.conn.OkHttpClientConnectionManager 保持一致,人为的加了一层 okhttp3 的链接管理器 okhttp3.HttpClientConnectionManager(注意:命名空间为 okhttp3),主要用于初始化连接池类 okhttp3.ConnectionPool。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/httpclient/connectionmanager.html#连接管理器-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.0/httpclient/response.html", + "children": [ + { + "title": "响应", + "url": "/manual/2.0/httpclient/response.html#响应", + "content": "响应当通过 HttpClient 发起任意请求后,将得到一个 Response。此结果,包括了:协议及其版本、状态码及其信息、响应头列表、响应体、以及响应体长度。buession-httpclient 会将 apache httpcomponents 或 okhttp3 的响应对象,转换为 Response。需要注意的是,原生 apache httpcomponents 或 okhttp3 响应体流,一旦被使用过一次之后,将不能再使用;同时您只能以流或者字符串二选一的形式获取响应体。但是,在 buession-httpclient 中您将可以二者兼顾,当然这也同时会带来一些性能消耗和内存占用。import com.buession.httpclient.HttpClient;import com.buession.httpclient.ApacheHttpClient;\nimport com.buession.httpclient.conn.ApacheClientConnectionManager;\nimport com.buession.httpclient.core.Response;\nimport java.io.InputStream;\n\nHttpClient httpClient = new ApacheHttpClient(new ApacheClientConnectionManager());\n\nResponse response = httpClient.post(\"https://www.buession.com/\");\nInputStream stream = response.getInputStream(); // 以流的形式获取响应体\nString body = response.getBody(); // 以字符串的形式获取响应体\n\nstream.close();\ngetInputStream、getBody 二者可以重复调用,当时您需要始终手动关闭一下流,因为这将是拷贝的原生 apache httpcomponents 或 okhttp3 返回的流。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/httpclient/response.html#响应-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-httpclient 参考手册", + "content": "", + "url": "/manual/2.0/httpclient/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.0/httpclient/method.html#方法", + "content": "方法buession-httpclient 提供了和 HTTP 请求方式同名的方法 API,您可以很方便的通过提供的方法发起 HTTP 请求。示例:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\");\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\");\n您可以自定义请求头:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.List;\nimport java.util.ArrayList;\n\nList headers = new ArrayList();\n\nheaders.add(new Header(\"X-SDK-NAME\", \"Buession\"));\nheaders.add(new Header(\"X-Timestamp\", System.currentTimeMillis()));\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", headers);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", headers);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", headers);\n您可以设置请求参数:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport java.util.Map;\nimport java.util.HashMap;\n\nMap parameters = new HashMap();\n\nparameters.put(\"action\", \"edit\");\nparameters.put(\"id\", 1);\n\n// GET 请求\nResponse response = httpClient.get(\"https://www.buession.com/\", parameters);\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", parameters);\n\n// HEAD 请求\nResponse response = httpClient.head(\"https://www.buession.com/\", parameters);\n您可以设置请求体:import com.buession.httpclient.HttpClient;import com.buession.httpclient.core.Response;\nimport com.buession.httpclient.core.Header;\nimport jcom.buession.httpclient.core.RequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\nimport jcom.buession.httpclient.core.EncodedFormRequestBody;\n\nEncodedFormRequestBody requestBody = new EncodedFormRequestBody();\n\nrequestBody.addRequestBodyElement(\"username\", \"buession\");\nrequestBody.addRequestBodyElement(\"password\", \"buession\");\n\n// POST 请求\nResponse response = httpClient.post(\"https://www.buession.com/\", requestBody);\n\nJsonRawRequestBody requestBody = new JsonRawRequestBody(new User());\n// PUT 请求\nResponse response = httpClient.put(\"https://www.buession.com/\", requestBody);\n不同的 RequestBody,决定了我们以什么样的 Content-Type 提交数据,buession-httpclient 中提供了大量的内置 RequestBody。" + }, + { + "title": "RequestBody", + "url": "/manual/2.0/httpclient/method.html#方法-requestbody", + "content": "RequestBody\n\nRequestBody\nContent-Type\n说明\n\n\n\n\nInputStreamRequestBody\napplication/octet-stream\n二进制请求体\n\n\nChunkedInputStreamRequestBody\napplication/octet-stream\nChunked 二进制请求体\n\n\nRepeatableInputStreamRequestBody\napplication/octet-stream\nRepeatable 二进制请求体\n\n\nEncodedFormRequestBody\napplication/x-www-form-urlencoded\n普通表单请求体\n\n\nMultipartFormRequestBody\nmultipart/form-data\n文件上传表单请求体\n\n\nHtmlRawRequestBody\ntext/html\nHTML 请求体\n\n\nJavaScriptRawRequestBody\napplication/javascript\nJavaScript 请求体\n\n\nJsonRawRequestBody\napplication/json\nJSON 请求体\n\n\nTextRawRequestBody\ntext/plain\nTEXT 请求体\n\n\nXmlRawRequestBody\ntext/xml\nXML 请求体\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/httpclient/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-io 参考手册", + "content": "封装了对文件的操作", + "url": "/manual/2.0/io/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/io/index.html#安装", + "content": "安装 com.buession\n buession-io\n x.x.x\n\n该模块二次封装了 java java.io.File 和 java.nio.file.Files 类,在此基础上扩展了大量的实用方法,如:文件读写、获取文件 MD5 和 SHA1 值,获取文件 MIME,设置文件所属用户和用户组,简化了我们在应用开发过程中对文件的操作。" + }, + { + "title": "读取文件", + "url": "/manual/2.0/io/index.html#读取文件", + "content": "读取文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nbyte[] result = file.read();\n" + }, + { + "title": "写文件", + "url": "/manual/2.0/io/index.html#写文件", + "content": "写文件import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nfile.write(\"Buession\");\nfile.write(\"Buession\".getBytes());\nfile.write(\"Buession\", true); // 追加写\n" + }, + { + "title": "获取文件 MD5、SHA-1值", + "url": "/manual/2.0/io/index.html#获取文件-md5、sha-1值", + "content": "获取文件 MD5、SHA-1值import com.buession.io.file.File;\nFile file = new File(\"/tmp/debug.txt\");\n\nString md5 = file.getMd5(); // 获取文件 MD5\nString sha1 = file.getSha1(); // 获取文件 SHA-1\n" + }, + { + "title": "获取文件 MD5、SHA-1 值", + "url": "/manual/2.0/io/index.html#获取文件-md5、sha-1-值", + "content": "获取文件 MD5、SHA-1 值import com.buession.io.file.File;import com.buession.io.MimeType;\n\nFile file = new File(\"/tmp/debug.txt\");\n\nMimeType result = file.getMimeType();\n" + }, + { + "title": "设置文件权限", + "url": "/manual/2.0/io/index.html#设置文件权限", + "content": "设置文件权限import com.buession.io.file.Files;\nFiles.chmod(\"/tmp/debug.txt\", 0777);\n" + }, + { + "title": "设置文件用户组", + "url": "/manual/2.0/io/index.html#设置文件用户组", + "content": "设置文件用户组import com.buession.io.file.Files;\nFiles.chgrp(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "设置文件用户", + "url": "/manual/2.0/io/index.html#设置文件用户", + "content": "设置文件用户import com.buession.io.file.Files;\nFiles.chown(\"/tmp/debug.txt\", \"root\");\n" + }, + { + "title": "注解", + "url": "/manual/2.0/io/index.html#注解", + "content": "注解注解 com.buession.io.json.annotation.MimeTypeString 可以将类型为 com.buession.io.MimeType 的字段序列化为字符串和将字符串反序列化为 com.buession.io.MimeType,该功能是基于 jackson 实现的。import com.buession.io.json.annotation.MimeTypeString;\nclass File {\n\n @MimeTypeString\n private MimeType mime;\n\n}\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/io/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-jdbc 参考手册", + "content": "JDBC 通用 POJO 类定义,对 Hikari、Dbcp2、Druid 等配置和数据源的封装。", + "url": "/manual/2.0/jdbc/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/jdbc/index.html#安装", + "content": "安装 com.buession\n buession-jdbc\n x.x.x\n\n通过提供的 API,您可以简化对 DBCP2、Druid、Hikari、Tomcat 数据源的初始化,该类库基本不单独使用。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/jdbc/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-json 参考手册", + "content": "主要实现了一些 jackson 的自定义注解及序列化、反序列化的实现。", + "url": "/manual/2.0/json/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/json/index.html#安装", + "content": "安装 com.buession\n buession-json\n x.x.x\n\n封装了大量基于 jackson 的注解。" + }, + { + "title": "注解", + "url": "/manual/2.0/json/index.html#注解", + "content": "注解\n\n注解\n说明\n\n\n\n\nCalendarUnixTimestamp\njava.util.Calendar 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Calendar 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Calendar\n\n\nDateUnixTimestamp\njava.util.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.util.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.util.Date\n\n\nSqlDateUnixTimestamp\njava.sql.Date 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Date 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Date\n\n\nTimestampUnixTimestamp\njava.sql.Timestamp 和 Unix 时间戳序列化、反序列化;通过该注解,可以将 java.sql.Timestamp 序列化为 Unix 时间戳;将 Unix 时间戳反序列化为 java.sql.Timestamp\n\n\nJsonEnum2Map\n枚举和 java.util.Map 序列化和反序列化;通过该注解,可以将枚举序列化为 java.util.Map;将 java.util.Map 反序列化为枚举\n\n\nSensitive\n通过该注解可以实现数据的脱敏\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/json/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-lang 参考手册", + "content": "常用 POJO 类和枚举的定义,详细查看 API 参考手册。", + "url": "/manual/2.0/lang/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/lang/index.html#安装", + "content": "安装 com.buession\n buession-lang\n x.x.x\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/lang/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-net 参考手册", + "content": "网络相关工具类。", + "url": "/manual/2.0/net/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/net/index.html#安装", + "content": "安装 com.buession\n buession-net\n x.x.x\n\n" + }, + { + "title": "IP 地址工具类", + "url": "/manual/2.0/net/index.html#ip-地址工具类", + "content": "IP 地址工具类IP 地址工具类 com.buession.net.utils.InetAddressUtis 实现了,IPV4 地址和数字型 IP 相互转换。import com.buession.net.utils.InetAddressUtis;\nlong result = InetAddressUtis.ip2long(\"127.0.0.1\"); // 2130706433\nString ip = InetAddressUtis.long2ip(2130706433L); // 127.0.0.1\nURI 类或 URIBuilder 类,实现了 url 字符串的构建,详细查看 API 手册。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/net/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "Redis 操作类,基于 jedis 实现,RedisTemplate 方法名、参数几乎同 redis 原生命令保持一致。同时,对对象读写 redis 进行了扩展,支持二进制、json方式序列化和反序列化,例如:通过 RedisTemplate.getObject(“key”, Object.class) 可以将 redis 中的数据读取出来后,直接反序列化成一个对象。", + "url": "/manual/2.0/redis/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/redis/index.html#安装", + "content": "安装 com.buession\n buession-redis\n x.x.x\n\n" + }, + { + "title": "介绍", + "url": "/manual/2.0/redis/index.html#介绍", + "content": "介绍buession-redis 是一款基于 jedis 的 redis 操作库,最大的优势就是封装了与 redis 同名、最大程度与 redis 原生参数顺序一致的 API。同时,我们在现代应用中,经常需要读写一个 pojo 对象,buession-redis 封装了 xxxObject 读写取 redis 中的二进制或 JSON 数据,并反序列化为 POJO 类。大大简化了,我们在代码中对象存取到 redis 中,让我们更专注业务功能的开。能够通过 com.buession.redis.core.Options 设置全局选项,如:统一的 Key 前缀,对象基于什么方式序列化和反序列化。import com.buession.redis.RedisTemplate;import com.buession.redis.core.Options;\nimport com.buession.core.serializer.type.TypeReference;\nimport java.utils.Map;\nimport java.utils.HashMap;\n\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.setOptions(new Options());\nredisTemplate.afterPropertiesSet();\n\n// 将 User 对象写进 key 为 user hash 中\nredisTemplate.hSet(\"user\", \"1\", new User());\n\n// 获取 key 为 user ,field 为 1 的 hash 中的数据,并转换为 User\nUser user = redisTemplate.hGetObject(\"user\", \"1\", User.class);\n\n// 获取 key 为 user 的 hash 的所有数据,并将值转换为 User\nMap data = redisTemplate.hGetAllObject(\"user\", \"1\", new TypeReference>{});\n" + }, + { + "title": "展望", + "url": "/manual/2.0/redis/index.html#展望", + "content": "展望目前,buession-redis 仅支持 jedis,不支持 lettuce,我们预计会在下个版本或者下下个版本(即:2.1 或者 2.2)中计划加入。其实,之前尝试过,但由于两者 API 差异性和使用方式太大,没法很好的做到统一化,就暂时放弃了。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/redis/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.0/redis/datasource.html", + "children": [ + { + "title": "数据源", + "url": "/manual/2.0/redis/datasource.html#数据源", + "content": "数据源buession-redis 基于数据源 DataSource 连接 redis,其机制类似 JDBC 的 DataSource。通过,数据源可以配置,redis 的用户名、密码、连接超时、读取超时、连接池、SSL等等。数据源 DataSource 包括三个子接口:StandaloneDataSource:单机模式数据源\nSentinelDataSource:哨兵模式数据源\nClusterDataSource:集群模式数据源\njedis 和后续的 lettuce 分别实现这三个接口,用于创建不通模式的数据源,数据源中实现了连接池的创建。在原始的 jedis 或者 spring-data-redis 中,密码为空字符串时,会以空字符串密码进行登录 redis;我们修改了这一逻辑,不管您在程序中密码是设置的 null 还是空字符串,我们都会跳过认证。这样的好处就是,假设您的开发环境 redis 没有设置密码,生产环境设置了密码,我们可以通过一个 bean 初始化即可,不用写成两个 bean。测试环境 properties:redis.host=127.0.0.1redis.port=6379\nredis.password=\n生产环境 properties:redis.host=192.168.100.131redis.port=6379\nredis.password=passwd\n" + }, + { + "title": "连接池", + "url": "/manual/2.0/redis/datasource.html#数据源-连接池", + "content": "连接池通过连接池管理 redis 连接,能够大大的提高效率和节约资源的使用。jedis 和 lettuce 均使用 apache commons-pool2 来创建和维护连接池。但是,在 jedis 中,以 JedisPoolConfig 和 ConnectionPoolConfig 来管理单例模式连接池、哨兵模式连接池和集群模式连接池;为了简化配置,我们定义了 com.buession.redis.core.PoolConfig 来统一维护各种模式的连接池配置,然后在各 DataSource 中转换为原生的连接池配置,极大的简化了学习和替换成本。连接池配置\n\n配置项\n数据类型\n-- 默认值\n说明\n\n\n\n\nlifo\nboolean\nGenericObjectPoolConfig.DEFAULT_LIFO\n池模式,为 true 时,后进先出;为 false 时,先进先出\n\n\nfairness\nboolean\nGenericObjectPoolConfig.DEFAULT_FAIRNESS\n当从池中获取资源或者将资源还回池中时,是否使用 java.util.concurrent.locks.ReentrantLock 的公平锁机制\n\n\nmaxWait\nDuration\nGenericObjectPoolConfig.DEFAULT_MAX_WAIT\n当连接池资源用尽后,调用者获取连接时的最大等待时间\n\n\nminEvictableIdleTime\nDuration\n60000\n连接的最小空闲时间,达到此值后且已达最大空闲连接数该空闲连接可能会被移除\n\n\nsoftMinEvictableIdleTime\nDuration\nGenericObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION\n连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留 minIdle 个空闲连接数\n\n\nevictionPolicyClassName\nString\nGenericObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME\n驱逐策略的类名\n\n\nevictorShutdownTimeout\nDuration\nGenericObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT\n关闭驱逐线程的超时时间\n\n\nnumTestsPerEvictionRun\nint\n-1\n检测空闲对象线程每次运行时检测的空闲对象的数量\n\n\ntestOnCreate\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_CREATE\n在创建对象时检测对象是否有效,配置 true 会降低性能\n\n\ntestOnBorrow\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW\n在从对象池获取对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestOnReturn\nboolean\nGenericObjectPoolConfig.DEFAULT_TEST_ON_RETURN\n在向对象池中归还对象时是否检测对象有效,配置 true 会降低性能\n\n\ntestWhileIdle\nboolean\ntrue\n在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性;建议配置为 true,不影响性能,并且保证安全性\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\nblockWhenExhausted\nboolean\nGenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED\n当对象池没有空闲对象时,新的获取对象的请求是否阻塞(true 阻塞,maxWaitMillis 才生效;false 连接池没有资源立马抛异常)\n\n\ntimeBetweenEvictionRuns\nint\n30000\n空闲连接检测的周期,如果为负值,表示不运行检测线程\n\n\njmxEnabled\nboolean\nGenericObjectPoolConfig.DEFAULT_JMX_ENABLE\n是否注册 JMX\n\n\njmxNamePrefix\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX\nJMX 前缀\n\n\njmxNameBase\nString\nGenericObjectPoolConfig.DEFAULT_JMX_NAME_BASE\n使用 base + jmxNamePrefix + i 来生成 ObjectName\n\n\nmaxTotal\nint\nGenericObjectPoolConfig.DEFAULT_MAX_TOTAL\n最大连接数\n\n\nminIdle\nint\nGenericObjectPoolConfig.DEFAULT_MIN_IDLE\n最小空闲连接数\n\n\nmaxIdle\nint\nGenericObjectPoolConfig.DEFAULT_MAX_IDLE\n最大空闲连接数\n\n\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/redis/datasource.html#数据源-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-redis 参考手册", + "content": "", + "url": "/manual/2.0/redis/method.html", + "children": [ + { + "title": "方法", + "url": "/manual/2.0/redis/method.html#方法", + "content": "方法buession-redis BaseRedisTemplate 的方法以及参数计划与原生 redis 命名保持一致。复杂的参数会通过 Builder 进行参数构建,在多个值中进行选择的将定义成枚举,规避出错的几率。import com.buession.redis.BaseRedisTemplate;\nBaseRedisTemplate redisTemplate = new BaseRedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 删除哈希表 key 中的一个或多个指定域\nredisTemplate.hDel(\"user\", \"1\", \"2\", \"3\");\n\n// 检查给定 key 是否存在\nredisTemplate.exists(\"user\");\n\n// 获取列表 key 中,下标为 index 的元素\nredisTemplate.lIndex(\"user\", 1);\n\n// 如果键 key 已经存在并且它的值是一个字符串,将 value 追加到键 key 现有值的末尾\nredisTemplate.append(\"key\", \"value 1\");\nBaseRedisTemplate 实现了 redis 的原生操作,RedisTemplate 继承了 BaseRedisTemplate ,在此基础上实现了将 redis 中的二进制或者 JSON 格式的值,反序列化为一个类。import com.buession.redis.RedisTemplate;\nRedisTemplate redisTemplate = new RedisTemplate(dataSource);\n\nredisTemplate.afterPropertiesSet();\n\n// 获取列表 key 中,下标为 index 的元素,并反序列化为 User 类\nUser user = redisTemplate.lIndexObject(\"user\", 1, User.class);\n序列化和反序列化,基于 buession-core 序列化和反序列化 扩展而来,序列化或反序列化出错时会直接返回 null,而忽略异常,默认使用 com.buession.redis.serializer.JacksonJsonSerializer 序列化为 JSON。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/redis/method.html#方法-api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-session 参考手册", + "content": "无文档", + "url": "/manual/2.0/session/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/session/index.html#安装", + "content": "安装 com.buession\n buession-session\n x.x.x\n\n该模块无实际意义,将在今后的版本中会删除掉。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/session/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-thesaurus 参考手册", + "content": "对词库的解析,目前仅支持搜狗词条。", + "url": "/manual/2.0/thesaurus/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/thesaurus/index.html#安装", + "content": "安装 com.buession\n buession-thesaurus\n x.x.x\n\n您可以通过该库解析搜狗拼音的词条库,包括词条、拼音信息。import com.buession.thesaurus.SogouParser;import com.buession.thesaurus.Parser;\nimport com.buession.thesaurus.core.Word;\nimport java.util.Set;\n\nParser parser = new SogouParser();\n\nSet words parser.parse(\"搜谱拼音词条文件路径\");\n" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/thesaurus/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-velocity 参考手册", + "content": "spring mvc 不再支持 velocity,这里主要是把原来 spring mvc 中关于 velocity 的实现迁移过来,让喜欢 velocity 的 coder 继续在高版本的 springframework 中继续使用 velocity。", + "url": "/manual/2.0/velocity/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/velocity/index.html#安装", + "content": "安装 com.buession\n buession-velocity\n x.x.x\n\n该类库,基本照搬了 springframework 集成 velocity 的代码和逻辑。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/velocity/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "web 相关的功能封装,支持 servlet 和 reactive;封装了一些常用注解,简化了业务层方面的代码实现;封装了一些常用 filter。", + "url": "/manual/2.0/web/index.html", + "children": [ + { + "title": "安装", + "url": "/manual/2.0/web/index.html#安装", + "content": "安装 com.buession\n buession-web\n x.x.x\n\nbuession-web 扩展了 spring-webmvc、spring-webflux;在此基础上,提供了大量的实用的 filter 和注解,以及工具类。该模块无论封装的 filter、注解、工具类,均适用于 spring mvc,也适用于 spring webflux。当时,filter、工具类均为 servlet 和 webflux 各自独立的类,不是说同一个类,即能用于 servlet,也能用于 webflux,当然注解是通用的。" + }, + { + "title": "API 参考手册>>", + "url": "/manual/2.0/web/index.html#api-参考手册>>", + "content": "API 参考手册>>" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.0/web/annotation.html", + "children": [ + { + "title": "注解", + "url": "/manual/2.0/web/annotation.html#注解", + "content": "注解我们通过注解的形式封装了一些我们在日常应用开发过程中能用到的实用性功能,简化了您在代码开发中的便捷度,让您能够更专注业务的开发。" + }, + { + "title": "注解", + "url": "/manual/2.0/web/annotation.html#注解-注解", + "content": "注解\n\n注解\nRequest / Response\n作用域\n说明\n\n\n\n\n@RequestClientIp\nrequest\n方法参数\n获取当前请求的客户端 IP 地址\n\n\n@ContentType\nresponse\n类、方法\n设置响应 Content-Type\n\n\n@HttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值\n\n\n@DisableHttpCache\nresponse\n类、方法\n设置响应缓存头 Cache-Control、Expires、Pragma 值,禁止缓存\n\n\n@ResponseHeader\nresponse\n类、方法\n设置响应头\n\n\n@ResponseHeaders\nresponse\n类、方法\n批量设置响应头\n\n\n@DocumentMetaData\nresponse\n类、方法\n设置页面标题、页面编码、关键字、描述、版权等等元信息\n\n\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.0/web/filter.html", + "children": [ + { + "title": "过滤器", + "url": "/manual/2.0/web/filter.html#过滤器", + "content": "过滤器我们封装了一些使用的过滤器,简化了您的开发或者应用运行的跟踪。servlet 包位于 com.buession.web.servlet.filter,webflux 包位于 com.buession.web.reactive.filter,均有同样类名的过滤器类。" + }, + { + "title": "过滤器", + "url": "/manual/2.0/web/filter.html#过滤器-过滤器", + "content": "过滤器\n\n过滤器\n说明\n\n\n\n\nMobileFilter\n当前请求是否为移动设备\n\n\nPoweredByFilter\nPowered By 过滤器\n\n\nPrintUrlFilter\n打印当前请求 URL 过滤器\n\n\nResponseHeaderFilter\n响应头过滤器,设置响应头\n\n\nResponseHeadersFilter\n响应头过滤器,批量设置响应头\n\n\nServerInfoFilter\nServer 信息过滤器,通过响应头的形式,输出当前服务器名称,可以用于集群环境定位出现问题的节点\n\n\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.0/web/restful.html", + "children": [ + { + "title": "RESTFUL", + "url": "/manual/2.0/web/restful.html#restful", + "content": "RESTFULRestful 是当今比较流行的一种架构的规范与约束、原则,基于这个风格设计的软件可以更简洁、更有层次。我们遵循 REST 规范,在代码层面规范好了新增、修改、详情、删除等基本的路由,您的控制器层只需要继承 com.buession.web.servlet.mvc.controller.AbstractBasicRestController 或者 com.buession.web.reactive.mvc.controller.AbstractBasicRestController 即可在 servlet 或 webflux 模式下,实现标准的 REST 风格的代码。简化了您的代码(主要是不用再写 @RequestMapping)和标准化了。@RestController@RequestMapping(path = \"/example\")\npublic class ExampleController extends AbstractRestController {\n\n\t@Override\n\tpublic Response add(HttpServletRequest request, HttpServletResponse response, @RequestBody ExampleDto example){\n\t\t\n\t}\n\n\t@Override\n\tpublic Response edit(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id, @RequestBody ExampleDto example){\n\n\t}\n\n\t@Override\n\tpublic Response detail(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\n\t}\n\n\t@Override\n\tpublic Response delete(HttpServletRequest request, HttpServletResponse response, @PathVariable(name = \"id\") Integer id){\n\t\t\n\t}\n\n}\n" + } + ] + }, + { + "title": "buession-web 参考手册", + "content": "", + "url": "/manual/2.0/web/utils.html", + "children": [ + { + "title": "工具", + "url": "/manual/2.0/web/utils.html#工具", + "content": "工具我们封装了一些 web 相关的工具类,用于处理 request、response。servlet 包位于 com.buession.web.servlet.http,webflux 包位于 com.buession.web.reactive.http,均有同样类名的过滤器类。获取客户端真实 IP 地址:RequestUtils.getClientIp(request);我们兼容了,通过微信和一些 CDN 厂商获取用户真实 IP 的头信息,我们优先获取从微信透传过来的用户的真实 IP,然后再是各 CDN 厂商的用户真实 IP 头,最后才是标准的真实 IP 头。当然,我们不能保证是否是伪造的。优先顺序:X-Forwarded-For-Pound(微信) > Ali-Cdn-Real-Ip(阿里云 CDN) > Cdn-Src-Ip(网宿) > X-Cdn-Src-Ip(网宿) > X-Original-Forwarded-For(天翼云) > X-Forwarded-For > X-Real-Ip > Proxy-Client-IP > WL-Proxy-Client-IP > Real-ClientIP > remote addr是否是 Ajax 请求:RequestUtils.isAjaxRequest(request);是否是移动设备请求:RequestUtils.isMobile(request);设置缓存:ResponseUtils.httpCache(response, 5); // 缓存 5 秒ResponseUtils.httpCache(response, new Date()); // 缓存到指定的时间点\n" + } + ] + } + ] +} \ No newline at end of file diff --git a/support.html b/support.html new file mode 100644 index 0000000..ba81a99 --- /dev/null +++ b/support.html @@ -0,0 +1,16 @@ +技术支持
\ No newline at end of file diff --git a/ydoc/images/android-chrome-192x192.png b/ydoc/images/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..aede5e3fe32ab15897e39380358d5e7342296b2f GIT binary patch literal 11657 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 + + + + + #da532c + + + diff --git a/ydoc/images/dog@1x.png b/ydoc/images/dog@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb0f99ee035974822d325d8eafb3b22facdc6d8 GIT binary patch literal 2368 zcmV-G3BUGPx-`bk7VR9Fd>S8Hrl#}S@A=NR8>*H8Qi5O9r&A%J7RA&9C30#O7%mW3UVs*v2oepZA`# zoo~Pg8IjhT&pEs?+ zdgCA`3YV{3p(Te)c`}+E1W@aXJcXv~6ZmuFM{4E$Ir3kq^suO1B@0HuwcngF> z>yED!^@^H*6w#c&$S^2ogKF@*T/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 literal 0 HcmV?d00001 diff --git a/ydoc/images/dogbg@2x.png b/ydoc/images/dogbg@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7c7a0d491c025e31304fef0c7a618131699ec54b GIT binary patch literal 93633 zcmc$`Rajijx;5HBLmGE?cM0y0;O_43?gS^edlG_caCg_>?(Xic`8#WWXYak%f9}r3 zx#-zXPpMh+tyxv$9b;5SD9TG9!r{RI002ZONl|4000syEKtzLJz+2;mrxNf12p46E z&wz??f+GMx7$7D3N!1hL)c3x@S-gj z7-WTv29PV#Lgo$KHjDE&xwC%`>BWH%4?r~p{>N7!j6?toaA0uIqneF{go~T%XFDB9 zmHiuxLMj_D7{cr>f*w-6CSYkNn_&c(zD~9m)D#PV%cd6MBgV!6;hBv}M;RW`vl)*Y zMF(V|u-HJim?YG+XHW^5eRccA1iAd9;TLVw#xH7y#x~V%$r)8|eqsV7xBa!{OCJ&f zZ9KcC^m9+vr*2J(>sn?0Cvut)d8h%UZUR7Xhhsr>hP;u60$5Iwk z)@CLz20SK7vL3N)LH(~9*ZKBwO6AP--Gg*nBUbpJ!0r{!yH$Rl2PO5Id^^&zN%&wx zZ}0Ta{?A!jO_}l+jiq57e}wQ9-ew}-WorQ_IGRG&sI5zNDuBC?1;$Q7!qa^PnTa)0 zMm)r0`y&M1(g{3FmZ`IDiR-I(HGwp1E1K~#lt@=?Z+AAx4zaE>k`=XYkNl67gBav~ z_H(b^g5ovNw?uOJLih^#O&1QpSmIvMPE2N>1ji@~enG!oOENFL`rD3^3W=7}-*`mp zZM|~H{F6BfdCYD0Pb0p-2+?QPa;C+APW2OuMr2f!5WUhLO5Zp;&>mtM?Yw<|-R{AC z7d&gZBcC|w{e7XHvv5OOkd@w4EoXSEE&#`d*TV+qBEug_bpfahGE!u8kY!mbyi0TW z@MG`YnSCz2sQyz%Mn>UNNhlISo;(T;&az{kPDyG>${k0Oj$ga1E2e`Ry^yxxD~HvT zr7#y91)Wc`WgtE#OKBeeNvOEK_R2zSDK7tWkG1##h3=u`8ZmYGeqUc-1tB3Jb4ubE z@h4hWJPIVn9$Wv7Ve{c}Mi0An8}pS>HUSyrtQav7MsqC!BEJ$l)|csIny==r7_m~n za^uP{+B3MdQn$Yuoi`+jD?}yam3Hq4JUeb3#0is3ZBAh8d#hR5jHNuCYc}nEAtqRU z4Ce5q7qr4eV-|v(p0lFQo+Bpy3_~2WB|Vd9X!*UWwF5OfNW0>aZ!BklI8dei_Gna) ziimKUd@jI``Q;R-(HvKr>_flCct(#amv4U_8>%oBs^yj>XLL`hx!3LL7TNWtL!{=& zyQX7PAXtxK?l13}8&swcc0SJO`|Z0yRwmcnH_|zhEvVjf`3;+ei~`C0k!e}iU)n3o z@i;MPIh`BUYC^u==SW&&74k>Pw)@_VYDGpGr+mM)_gb&s84L5>1$kTbs8w=WK=!b% z0qlv`r6LA8PPf}NzV3FJP&Gag^A5Y5KRj4+OVe102usqq_JkEaTC0pZx^=aXzz$W@ zi5#kSWq%!{+dyL&7I%_S#L9i9Kv_fJ3$Zlx+|7L+wKLPckx<0->yVAX-KK3_BN+}) zRO>h&LGUWWz#kxj(uKbtiPK)C`Vf3kL*Lq5Z|Bi!^>aFnHT4LpVn5C<$U65dh~aoY z8IQ7gZ~RMgK@@V08G=R$)(S9%?Dvr!sp2tIHgez52NGM5&M-UZ;|dV*n(V{8gJ*Xv zp`z-cPS6J~5XR4#kVt@=)G30{Hu*Pn%{A_}Ex?xhbshNv(jV!^<23Q)#B>MPDwd-S zceGUFVW)M{fbYvj0v<}GE4WBvjdVgZi9^ze9?BvzE|)9<@@8+h5EN)CU*LOG=`hjl zD>EZRuZ=4``L?3=08i36s?XKc)fv+LUvR16+TuSt4*K#b$9$KSKcnv%7P#Xdo>Ex~ z-%(UxYmv6LWy{1k$24dkeU&K^6Pr5BwFoEq{!^1TO-cN&&)lzwQBplY(8g7z`8xq~ zPkqbG0Q~jNf_P1xllGFHo?fNp+nnnOqKTQi_8cXe)@`zzfT_%hUqF~Nc6=~3O-
ByDJGI|3O>S(P_sDpCH*3{33iHb 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
&=7J-<-@U41ZzfHb;s6ASl8=xt19 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Ø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 a^cY@=3zmFxGMx&vg2B_(&t;ucLK6Vo`>p!` literal 0 HcmV?d00001 diff --git a/ydoc/images/favicon-32x32.png b/ydoc/images/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..5dec63271b9ec5fcce80a7cf4556a7c811a69b92 GIT binary patch literal 1784 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 literal 0 HcmV?d00001 diff --git a/ydoc/images/logo.png b/ydoc/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..caca95f51fb0efccea29b16a3f127799499a097b GIT binary patch literal 23290 zcmd?Q^;?wP^EkeANk|CN-K8iU3rKefN-M|%5&{d$(kUR)DUGxU2udv_oeHcVA}r++ zE3vXH;F6!4=k>gQ{r&^ry)G`Ud*;kJGbd)w#7Q(Y)}f|kr38UM)OxxP%t0VRX%L8@ zpPUqc9B3Y{0{;m7&2=;g7RJOQfDeinx{v%pAS#CIKLSvGAqxofxJ2)Px@Az|UJ+Tl z<=k8^Szk2o+qdfvce|lYlY8^@o!ZIWgSVc)zpY+tPEt!qh%jR`d+?w_Z|hx+?wH%# z#3p{n;3u>E^hcr3zZ4f>f{Xl)Csp0{{5MZjr_MsJBIx(u`#;OQ-zi8BF!2BJDZKGRb1Sl(1nTr8hhXCjRY`6A+FZ(_BS?2T6#*h4> zLx1J`Zo!lm%uerIqv^=Z#~4P_QCt!N;&GvTnUlN17u(n4CeYWqJjZJLh3V=U$ijZQ z)5BilcCHbT6Wak2wd^}K{6#u}8R06|uosDzepFav>=X^Z7{1VeQ}6#a&T9g*WNp;e>B3~4T0niNm zT2G)&e_L~_B9mqj%L8jcG%{b4x9SPd^3N-zc@l5Pp$1&2(ALz~V77{k>dX>_G?&;l z_U3w=7#RQqi&SK^xoQhlFeFKD5dA&Q2tT@RDjipCZa!8kGVAJ6KzPST{i`4M zcTa2!(OE2R?|Rlh@?0yo>Le!M0tmNtQ@0N{j}w`{m*TfI$P8r3b|>csAl|y4RLiLo&r9Pi#UO zK0n(5t0brzG;P~PR)`2YrL{#2|V#$(tsn0C{n{cNVB|!s0 z;gw(?w_xK@c}BbR({fY*W{@%L@8SpzWIe8HZtKu)7yP~o8$eV|dTw;h?4ACLnOtqW z?Cu!@zRDJd?kiwVXkk$^T1`^|u%F3hZil)5RziKD2z&fO2@A$PZtjcvp?~C#<^^W( zGY@OHLoam)7mOaJ3R8j6E@ZMB8w= zz`XQXS3E01j)L(n83h)jfdakLme_yD6rblIAKiL)1K4@eWnVi4V8SpGg#4Q+wriZd8*hBb2P76|`VCRa$aTrcwxaen!ME>kQ3rF- z2;J}cE_7xY+v9thN&`Vix1C>ja`^=Si<(CL+tfBKKD{k-Dy`SQ^jm6Y*JzL^j0om* znxrdW;$Ir`GLD1K&XoYz&F<3{Y+c@Q&`l>%-Q^W)RfnS}q9Pgb-8R%1A^=doAB*@;^sA0HPHeBKpaOx`mK82iChh z{aPSyH2wL{=}ALBRKAe8~0TBIiin&B`d5_U*Uk*$Ec<_ zc72OG-kqkR3gX)}M7IL-Ral|UWpU*aqe^Gg^)c=k*_;$4bi?3dCPWE98c&zDsjR4b zXYJcLe~`3yltSYB5k*4CXMiZevt=9$rBfbwbNJJ^wRR_oR@0FOuls>np!z*1`+ORT z5>MYZbpG4$gKq3*-qe^C%CcDqrt&Cvy3HM<6Ue`%MCX?Ggpm|v%4xba_p&SVJR2fJ z>5AM{KlmzFvXb_kzROiUyDM5bVKQtWP_)D7wxCqp{DgqliSi*{ZEW zEFOOMHH;7n$=nNL{$q(H$MT`KUVJVm^wb7SgEA{*{Vtr~j=GAL9KBvRe`^G>7>89$g5YjWER zeFUovu7`k8m7>{RnKiqZz}FrUDsNYN4FJQOycv&jK@2C5AHy-A!oqBT8UxB4UEPiia1_3mWw#c zyV+TtzRs)SQ5x|P2=n2ldp>fmYw0Vb&OA;$npKb&w!B{1B&BZ?~j0;pt-*-F}gFVrVD*W zN4l3r(z1Mck=}0eCt~a&VviXx24$odbbghPYC`Af6dSu8j-$MVQ#PCFkCltkAn+S? z>eGiPK5F?Xl3|--(cc&P$<<#BXJW*=eat_rV0$ml6VuC!Xyy}toUJf8Kq74-{I zL=~$(sz=4;3H@8DztZ_i9=}>-Izm&6`^sv&VQ->0HT7&RC}wj$IMaGI*ng$r@Zs6t z-A6v(VHcZoLAfRkmw}z$OcDOvP2}IiDi>veOGuga@jdKQv@xlMEfzQ3UfHrtZDJiV zVn&Fls^b(vPuKM?9;Ls+k&oTMl>i!Rqe!V?yffIN1&TSm?||vJkB%5|L;Tes5#0z;E5u@Yn%uemqeE50MV zI6&OmrbIszLsz0+viCO!481&U;Rv=e^y8i(M;ulxDuKW^{+T{uKJxq=1)+nl_eMy! zdQ=f5YMZ9ydY^zmH{)5yyT|5e?){+af99ala;id>k<5Kw1X&~lB}mVL9Skq+;ps2w zHjm#oSzST6*U0<%Di*;Y@L;KS(|vp_WZ(vmxcJHiWHr);^F6c#VoMH6pqu61H)zQA z&fy4aNz)bG3qL+tT@xOq)AgTjfbUUR>$kTYq_F_!l{BUvzq+Y^K2Z`==; z7z;;GOUHf)%57{sIo@UKv~5bw^yVS}rD#yE9HTshI7o5meP|)=sX z4f2>f&&s7aDi#d^LpS@uOFbpq+m0u;?`N2CG6doLO{Q|u7!=7pThqeFY#{fZu77od zLvIg0@=OO2gV+-Sfyq6l6*Jf>*{sN&gB`Qk$u)RNx2*{j2`bfoR|4S! zz~2rIY{)+9!;45jHdO>;RI$>jp`tz$WJ3&T=3$QTdH@{DTCIvm=8K&Uqh+DP2S_{W zx3z&l2KSu$Bx9$m-BRl^jKQAm$&gMekZ?LFzZ9JYkx!WHEwZ0`lE`C1kbWd(*x{h7=?Mg@UQSen%A`k~w3gIXTrB zDQxN+>M(>SlY(5LSo2iaKG?Hru%uO|CWaEm z_&b4KPv*Z6*NukVG}Nj z`~Wb7xtXe^8rA1D3u@Ph>G`#-hM;DPmA>3xki%M2>%vL47;+!}$(fEngjWS^EL<(>4XI*IS31M@*Wd-)md zE9iqq8|^8f@9%O5nW#I*4mViw)eZLkq6dzj1X=0(f2J*T>MGf!Y#byV9Wm5}jypv% z4daVVI`>jjd{#d8cRGb;%Y~YG1z)_xDJ3_bU$*YCe({?ge1zCj0J&-rdB1tdGgdS^ zI@GcZGkG{v_VF}j4{jaAZ1P?|m0z!SOIY!l9*XwE(-4EUEl?Kq?2BE^OAGix^g#~u zs)J1Z&&3Y6u8bVJ{l#A1?nC#!pNa=iE+(lWMm(DDSO?*y2y!L5&rVgzCdpy~`{X)( z{uncRGuy1Uu<@U^U}5yN;VXdCCmNPD_bc^P^h#0fOChU>OmCSE_*Oy&NPY=nX4mS! zas&^()ok88&=mWo^yb5#lQV2c`enHI-`fisaCV{uL(xNy z&K8MQ6(_LkskDmt=|x1H&d`(7f0`hjG~h?z7#a6>({kS! zNI6M48gX>ATtD@&I&~ge`Z1xh%QguraS+Ul#wgv8N*wu)#zDAMrhvs#0yV{n+H{hN zFU7sYvLO0?Lx=q$O`9aZavemGZYBAgRm(yIJ-W_)u>UIV^B#+_f0!7+L`)`Q~39uB{14Z%=i zURKw2-=@}myn0gf>Fsx=?U}zfc1Emv@}s7f(f=YfR(U{uWu$H*dY0>0Z%;J`9=E>>o$7heegXnV zTKYZ568h2OnYozU)ioMJ+!t_DrX+A};B@XQK%DE_sJ=oHD|KiaaN3GlqmyBsUzI{l z&#RJIHw+Z0x?;Xnz;A;3`LaEY93H6ZTu$h0;FT2el(`p6%3pmX!IuI02zk&FaP))K zUJxreu-rJx89S1QO;K4d5cauvaW_mcAnN4veCsFjP9*~FAoH;=qu)lWN2Nj^7MhHP zjLG6J?^)&BRwx;6r<6O?88zmndE=bhvFNcjWsAKpj~JF41XT0a-ZveXWKFSTLQiXI zx8mn2L3GNssBjl9tVImv$Mmk3OK+!3e5T8#xD25ArVh(^-#N=&X;CQtOwIK{<-C`1 za+`c9(LoSAp^b55+i$oFOHiu1@@3H(A_gLkcX%D*t%0FA{a*U^UYc8GPdpFX3uiw5 z5(oP&4r%V59a<+}?_~jW0BZwnGfHzqf1z*CLO7;|uaV1s!;3RBB>8-lIbTw4u|Hfp z4M_#+OVjr@Sk?)5vC&%=Or}z?%6f<*16=Q{Or?sDl14qKC4yQ7;;0&cA9T)M&bD~h z4Ol2WDZ0FftK7pjW%4|@26M#mj+T$$aS1h zh2av)aDRNq`L89i$bdRZbW{ORBz(hhSv2I!r{B2xAw;rLtnbM%dZqJhbnuraL=N;r z-6yW6`26Y3>@^jvi*1?dh7Aw$P5+Cc?y9d!>vs%qtFyf9DySPwy65)814AI3Y>Ab} z9`TCy%enaY8365vh*2*a@Xy1x_aIZ2A@fEG}Z@lf&AqUbOp|YAiPf= zQ-o_4KT+a;$0$>wWXX~e3R{}9s2gJQC!hE-a?7Tp$~kZEmBvrz_yVX5EaXUq1EJOM(`JK zQIK3}`t3;fS7<~eLE@?Q7JhBy+-x8i2pBHMxRjQx`+HL>@m zwbOFCQ1}PPwRV*(P7vWEw?CYYic0OdrzrM5oMIq8Q9A5U4E_{V2uz~u|uZYp2f!}Iic0GZ!ESuE`x`#E?@-R7Uip^__03OI3osJ`G zx6u1bqQPX#W+J>R!05mO@I3Loc*@gfC`^TtlTBflR(>k~$SrqrWB_V?THBG;o=y9T zU|m;qp?r`~O&Q>{bY36Y|3RMlef^;*=k@N84B1Va2D(;}e;-af7-!imfbXQIx>#_I zGl&Zzp?PW2{th;i=MqL_Gp{DEcc){=!iu ztIvcamRcrOx;HeT!pZiaP8w({WIh&ti!xl8?4}uknj>81<>iJ1g4y^pz-YNs-B&%I z&7G(vn9GxD=~Mdaj;#ckFVjl} zlspnXwS_NIR*Jv;*-JC#BtL|< zO>3P>(BO?|m|aWLa_5@+b-y0iJq%(`har^$u14U?ovr-YoTObEXlkm41`kCmgl_C9 zV*Zu^`&`=NI{anZVb(+K&Kt-_zy+MwNAHL=V{T+pLJWZPkEM9tkWkKS@LtH1aq@m) zMc#AG9ZLKm_Zh>rTm#}((Wwjs#e)VUr;KExOrC-z??TRQTq_Q%=Q>JFFXReB$mU8S z+tc*-pETW_C1nz1+Ek^n6#nr*)k-cA*aolP*K_GtL8tVZh3-`bZw5o=D~13FH&^ot&Pb?_`2^RStmX}tg-aK&^%rCo`g z*-`hOE58y&R{i~BR}e;#z*mcE$q^g9kR2%3Q2rM6%JMsnV=SOahKD8ul~e*|XV+xU z=;?IK^|ZpDH$|T-^(}ss0OGzTS93tyoQ5B!7<+}vpU>C8+=WwG93T4i9bdEko1%>^v0r(RF7Jia}|t%tF>JXw-v@Qo!_h`=&u z2z9G6Rx7S8iHND@Jt{5?Fwx0zaJu2iBtiL*O#{`&wz)4+?HPdi3+Dt^z9|-VHj`pn zj-#qxt_YV*lx|ed?g9zJb+{m1>Q*P4y7G;2x}T3?{6Qc zI;dQHLyr_Ib)X%5W$9-V$>c8E(XwJ%GhM}ABi|GN+|8HKCYFhE4M0Ff*$G_P7TO&e zV)53RWMMDrdUxnvN_?iA0|)+Lpk(@~Kx0|>V=M`RCqJm?%JI1l?{sM8x&+IWK?tV?B@^$zU7UJ_WO{wusiE z*a)l}BGTtKD5EHM31}?yf5?YFs)yp7cCXtj2Rx{3`x(_blChHEHLy#no(1G1`FbhK z-Wmz?T1(e{a6p4A6T7Cm9k%9@hZUL0C&ui9t%43$5{G8ZEOz+e8gy3XVx}=me%BN0Sy*i$Ynls_HtLf zbv?DJ|FnvTNze})t->!!m($p$%7&Nt)=?;d6o{@#*WZ5-PXC0&uI*@G%%bK)_y#yG z@%7W(@4@%W-@QQd#=3e|uQfeBK`fb6c52dT0SRFGwfFJ(#_H5stV0lApnvXr$@XxV~-KE7pz|96j3=t4#kmKMRvH zYBZLb&Gf#<6N~k;Rq%z0&v)NzbAXExn+as^3YaP2!;Ad^|9na|%ZGOW%5?3E7u|hJ zzhlcCyK+#r(4>H=OrzC>#&Eye*6Pj_5c}<`DcY^tI9s6vArrMI1wtAnJ&}9siB`+U z$U8p?dCq&7QyS@g>NIffXm7d)x5Gw}hluPzvftB<`Q{v*&fkVBHk!xg9L_j(A9Kfc zs6d=5+q!OqPu};fhcJ`BH5TpA(r}yfX;BRO>B(rl+KUY=nm?+{$3^)s z25KP)dBk^(Rm2d?&H8PQJZC=5mDj{6#!ref8V0{0n{LG7Rivog?0fdw!{_hY$36}Ti#K43 z+jzO(T{@2SN8Ukj#W1(O|abbmQ5u4S3{RBM1@WF1On{dWF4I-OA!A2|EG#bGM@c~>$UP;?qBaE|inC z7l;n!6h(GN`Eh4^Y6bFK@gZ5jaQW!uncAdcZ zrC?oVa08-^m#TmFjj>#aWTIF)xaiklk$5!SlNI?0A5QK%hMd(n|tn z6@6XpDpv7PI32%6{CX5j{!3uR2Zy!QbadxY(ILr^uB5GHNE4Wz3X=j&2Sa>{h->c0r=@{{E}hiE$ZFCZ=j6Z7qF@aok6 z-+=Nn5BnU9Qta$@ijE&-w(xRm0Dk>ilr5VGDFOO`koVj7NN|6<5Cf zhO}_pHah8F#@mZO--C_;Imo}f-GQ4mBci^`KyNlj2(g^uNuaSDxX1J&|Zi?nE2@o35P{}!g4h^hbGlGFo>?-Yu#omaxHZ}p3J-Ahn7hEtIYIJZ_d}ZIce~Mw3245jx zjfi#2j99|l(#Q}I-}|GCLH)uLN1mr1yM|aEdX`mEYMzxi;+8JS`05XSV^`f{D}Z6M zKTWBXxT8qyJhMN(Shu21Zvp0){!CpXEg4v<0X6`1y`;)rKIBa>ixuo$5S|nd-~EvJ z-##h?3~yx4zacGHP-T!CXkxLZfmzATE<1=L@W;%5t7hz9s_{AUJxB5k3rj;+5+>_r zHadv`U)r9@o3Fca#NKMX_2R>SJMSql7t-7veChk-POx^jl~=gLAO7D%q10pR(4Fp! zQyt8bfbrl<$-%D`;(@vUEp`GC6Co)-#wG(G3Po00#{r=+4k#U%DLOD}>zCOvA-AmgFpHXF7j!rCz`6vROj0g!fpfzUo9BqpjDvTa9}p zl~8;Cr&hKAK?%m(Ms%Sd>0)fcxcKbMU;@%MM|qpSLBXeHQC^ySX31y{S6?Plb?O4-LY` zWg)0lU-v?9!5k=sRQLGWWzefSV%-}%8#`^KUjJ!{rG@M9U-V-i9}II2JF5Pw{Bc(? z{E-*G$xgRZ=tA-ASqr3u>JdJPf@J3BBU;ERCyo1 zxT|6$W z*Nm*7?O+?Gfq~I@NwU}mNKh;t0?s~eybcrE7E{=Y_!3|g%IhPtRq-CkBys%u$1lg^ za(UcSWs(YU!SpZxhGe3opLUN-I!@}2t7Z0zD`%?e&Smar|3d$$=ItKX1PWywzSM|f zmW1jM7SGzg)EJwTP(Ly)P|ZHBydMDA(#@%^&hmJM6x+u?Z~f}*r&np1W|YE&MlH1-<3ofMzXu5^$7KXI`Dos=^m7bcCfP zMJsYeXaC=9Q2e`^DyQx9V=Ctv-NeuO9-rL$^lZ_UDK^*G?_MKJ*>_+Y3a>|}1!W#ALUk+j@H`Ul{xTiC^Iq;r*}+Q)xy3ByLljKaO9jq)e?QxL zcD5Y=Os;nx#p{W-Uz1}V*B z33aZi=0;!4tF7GMeFoGVH^}~QIK>RoZI-o1J zx`6s<>#16eJG|GnelTv@yX8O9(V>dk?mJ!?7*9R|ylQyIzEG}n%4nPu89JOY^1bQW z(yR0DQTl`uL9OK+1$$d4wy4UI+S<@7v%D1j%QE?^&!fM3fw8%s$Kb0m`=j}g(K`D106AsMF#a`HV!f7^fLec1U zn5cX2rMIfTtgUJu&LQz~EwH@uf25x}iI_+pfAEq!Vf`D!)w0sjne%yfWhFf0;pky8 z#2=n!65Sdg>f=8mmGr%#V)5C7a2#L4SlloTq1b_&a1}er5nE-}Ja$_J^AzeEG}A)W70| zW{q01Fy~y|6CDtXdR*XJA||q1JW_dC-k~`FEO1vI@ z;H`3XGu0~iPiEy|m1lgQ*QFXEng7zJ`svCob=dXvT^_cE>t1%`iya|X@P(aJ zu*PE6bG=Uk%pGRO_+~xTt7op@YP$Gc1;oWokhW==%5>B#qNFtDaed(QdCp7Z?F}KO zvW;pn;oBN=ihn#``i^Z->ZjRKHWDd%>sFo^YNB-PSub9C3L&`&oNh2JnQ+vAr%hF# zYr#K`Z>nDG3Q2c0XqP8;4rQ#!-FuO954T)@$aI_WKnp?btt)W!QZKw25Wuqkt3v`X7+$)N8i zxC%yxlSjTS?GK3|Vj^dSx0P=>7v34ZyifQm2d?1S>{7IJ5-~|9*Eao8t$^vAd?=h} zZ&1<2$@SDDGEY!4xmW;6OJK@q8FUf|j7IMGgCe_HI$&-Rb)z6D#a+tRa2i!hC0(E;nHzh;Vh`}?a1ed_{5d+z|$^r{fPr7+> zyE{Vwc?QL#Jq_l8qWEeO#{v=A53Z>77#>Vmk2WrZ=|wi=iU_P{%EP$<)`|x5SNF_& z8izMOHVB?<|JsBstb?Sa#8Hvf$ijzD+bEp@vRjgOH)2lY7uL@zpS(yhXbQ~Uj{pf% zhz@Qknx>bUOhI?K8>@b*#+_85_fzia=0u6mr1m#Wb#r=Zd`^^beCFX-uaT6stOeO` z7saG^xXK|iCVo|%ae?%sDGy{()-;xa+XE^b9O+JuO+o1#KGxac#ddxpB9BP_su=Xf zguVg_>ihDC+YTZSx3=}uz~f=CR=i9~erOwqK=i4*2rQMc9m zp*rP5=NYPIWQikcZLz6xf{3d@@PgA1hWN7ztIo+~%nEv_CmGvGM&(z2f z*UoAa-gyabr<;q>{P{NNoYyVyo6i^x;({9Vy)61dj7*fpvua|8gLQ);Iwt6NgY7rq z1-0L&w@}d!O#gijT|y>*uYnXrDcFl3r$BV%mhD^trO|!Q;ELG@;gY2OMC~+8q=_T) zln3wT3hToH#U<|eV^>w1{W!Vb2q8F@z`cW+bM*UwIfc{cR8U9|C; zW%j(gjcc&}OP|Y)76A^l&%r6pvf$)aeO_Vf?|ccaS-0U&I}Ig?jasX2D860%8(5pM zL^qKt$FWW(u5+#BL6O^h1@j%+GN|0k<+V?S(Sw*AXgSAuO~^M7BP6|rZo~LD9esES z0k?EbWbYPOE9OgA@Vid8&z>`nppOy0V7c8NWJy6BNB7Z}B$41UjpZ*?nyGm4_f3Mn6HSUgWWU$ZAcZxpORWpDCi)j19DOYi~+sX=F9{r&sG=1GkwCm5LF;FZa)A1rl%?no^jLjNJW0+&@!G4}uk?KqiwByf{DX&g=Zy0NTC>C}<^sW@9 z;@8k(m+l)M)~&cPgZYX1-n&>{bT~<*X=#5C5k8wJTk@7qh zQ&onU@C$$FLcaO^`3?|l^)Gpk^)87*336Whd8nXL?>#wZ2~P_R}h-oF?su-a>pQ8 zMIU>h55$3(Y;35Ub^YvreT)x!{QI=}UAKe(bA%daj`OTwu8!`B5GaGaE}342W_ zFZO5F!Gy;*CDCh_sz|vD0o2-3HpUTA%g6pX^LsL&CMdeCGt~0!UAMQt2SQDDJoL^1 zE4qgmbdz;_^`(dE0t5&N1^fz;S$`ud4o`r{o_^IOou*^;*XArq(6Z78=Djz1p-9*NV&gey(47kbAeNz1mGL*Kr-w#3BzAQI=`~PKgf3f#o&bAb#SQ7K%a>&fSL>$DYeLXcn&wK5iEMMS(KAO)GSQU6XWCDhd39mrMPJ zUZ+~{VD5h6S+wBP;PMO#*7?Lx4xs(TM>z(M@X=MMq>`Z{? zHYonSSu3;*{&2U@ET481h@n!03Zq0H_$)FdGmogWmVO7SuLDkJe}1_Gr_U)#=MsXv zZHf+k6Z(ohcbdc91nWkDT=1(7xd{_|^k%x~=n~}(0NTJ?rE=dZKr?5&HIJ7EW!0kkdaai{hO~^Upp&E@=K`Ld{&w{>? z3Gtr@;G`C0MxWBd%XID%ju%+^)x{PQDyf!Li!|zOqr$)%jTGy(4RQ>@gT9$WM@kUr zj(vL1bOt6g7+>E-@x>a|fN&H6ebr!cdH6IAwy_6?R4PVR`mYSHi%FGwHt_bgbn@5J~;@Lu;xX-@|;-_!Zmhnjg_G;P^t96)F7!8Pri%a z1R&Ci46m`6QC93L!?%Y)8kmHTr1Kb}J>xN~qK1Q}{{rf_50DFiN&FNAu>u)sJ`=<3 zy<%mbi)2Wg1dqbnk-z{$@GPo1*1@`2t|JcjoD@X<*`mD`)kD$UDQTN=s6+Ns$^w?} zrB_w-izDWT0Qoq180qjF>Km$rqb4m5<$ouLK-`CDUvv3Eu{LMgXI6%PUpLwc;c$Ow zM{OB`y=Y9VyVLQ${K!kZI~Po1rV^EcXk-9&R%8?i;Vdb9`ob2YDCd{dxYY?j@`(e;r4j-nTqk?W;IkNwwRxr-Dt(@^SUI;rHY=E98i&UsZalUyFIUlOa<#Z&0 zH`6F_>t7UykrDdzqSmIoDjk_XAjV`>>n$5BeY1fE7k~S`GL!lo2429^*0DR3gPxa%MT}$T<{Q6RhQ(54kv0#Q0e*f*5)6)@m4xi2zOdw zJw_{|w%dQnvye6&&%`Nu;Q{U_XRbN;usKTiG0Z_`0NVCvK}b2HAKJ%WxauwnyurGh z-jm}ZA1$9z_LC9GbUK%)Xg#;42(u*S(*2=-8W8ChImH7cF7>y|H`HiroDM}#s3}w~ z2A`V;?JVn26h;B>i~4}dox=}QrW9U)NE{zV_iM&SByN6T-905 zoVMNa^!DFmaD~C$A8V|zOzQmPiuGg%f!H7TjLP53{0B01o`(EKp=r%_8kU~~bqUA{ zccIgQ@E*L-aIAPHW42bNUwpi___9fZ0|a{e70OWHP0?-OVc-9C?nbBsO-aeeM%+`v z+?m5N6@A^uL6DcJ{SlIx=iFulph&+soo`2}Yh2Gxa~`1@(@pkiDsG1utzgGy9PY9sK-&UoA3D~N%7<<8OVjN7IxA1mV8gbvw-xUj=II)`2j zod#-v^pSahbXvs))6aSF_ntV&@49uKD7`VeimBitAdXUf2RB9ZCslvv$2t2H-iGS` zQ2=N_k;K8DHZ8Hnei|B$^yzjuusDl7{OFwDC3aG0WZ#K8v=?nLfiy8Ek7= z73jqSZ~H(t>QXGK;$>2loH=?9teLEwO)!OTSI+fS7Tca#(DOgVp*_%TBR|V$E^%$G z&dhWGcUTRf`y**`ZaLb2aNkzO#!%>Ow#sQJ?Lim!3SLHD#xU>+VVltPE6|0tn?Qy# zE4GL2caz}?tPo67Z-&4C-?^sYLBQ>r1AVQMEmTua=h5v+aXSCq% ziu={%i4xCY1Q(x@ThC6g-%5KA?aN{>G3?vPkblI9+ z&1)5qn#_v7nKO}JYxf;^?V4t5(95RgqFCWl)zH9wRa9IixS7Tr_$M zG381WoKlC)x^m2%;~DvQAFq)T^gXS!;Jj(zZnD!I$9C$g3>eF$>kq}xClBf0{H0Wx zm`r;LN9q_d^uHG2(!aX9?%3l!nyaNZqMgQPoFJ&}d)fQn( z$oU5nYU%W8ne=m;G%hU^j_b147u3;alb(EDtryX|?})V-45HS(%5byOWIWlK3aB0+ zW=Q+W=hKwi8)1$*3Pv9tzt^JSsF_)xQMBljZ&*f5CF#^?*jOLWAS7!l$QyS!QWb9r zG7Kx(WyDS3lAP6p6f5!?StH zPdhLK=`2Mp3tpTrl(x%SUq-C{8AZq=qt0;~Ti52Dw+qwGWPuX_hCLB5;oJZQRmCB%S)zj?2c^;CHvj%lKbD>G&*tDOU0HE6Gq`t7y2h zw<-3YNeKi-keP9DNK2L3qzuF0WEXxG-I1Yl6Mc<{i+%uqlm*nrJWbDAYCvnk`UygfRW?qawja+XALfpHb znSIu$qJzr9O?90-2if9?H#G&65#W+5kDI=S!W)K_%mr>7msY1_2K5Lsj$2B8Jc@Vc z5)fOD85ypW^h*jLug9Aw^w9%7DS{X)Qfe<51!u+;ty2?kXB}% zNr_*2eXm9HS56IzY{1&Pmn93Kz$+q>x zL1z{=El8V-dxtmOND2NgP7x@hS#i%5!}Jbq_%onHQ+BuHU-BM3-a_!ac^ z2_q(Mu9jZ&>$5JQxu>bLppB|tm*|;@kNG{o55qQIzxIlXr4iRx<(2F^7W~mT;bta(#rP<(2)FbRcwRO&8XZ;;P5SU=5{%8u`@X z554R~$1F{5ugKCKdviskdr?tDqXH46QQSpu?eush9-&OH6W*s~Wo(+u_mxsE-N93C3P$j}8a*ik3rT3MkD7de~1 z#@f#IqOSZr)O{yZNSn!W?3SUe3pM@(Bx5(h8XaOSfG*a3m^{Ry<1Z2|xUf92i<(E) zy0>TDSbFyB>t=oXg5o1)|Eq#`B{#Cbv-JQZOYg z#g=u?&<}?8df}F$7RgKf&jZlDkU0?Q{a@QZXR+lIW(Fk>buxU>gWvu!ZK~C^k0XXW z`E|7uPxe3>ha`pyg0qJS5oKu~apg z7t?Dx{n(`AUpY&8NLhC}Kv9rI9Xs))R{F?sZoN-y3v6op-qL-%`IpgQ%K?VxPOGx8)49B^(e_Uo}A?q95S9<>wUGt`6e&Wu=ruq9JcM|X{x9t zSvzGauj0>vz(Iu+ZeQ%iWY*1`VY9@CPD$om*6@jBN;u zyX}JJ)Ta)^a8JdPUGL15KWZn3r@i&*Q%BF%KSwlT2rj^s3YNx(q^GD!@dEeW2LWN^mVrS%jM#rgtL`0fYi*9wW}hk3QWw2y4iaS{ z?!FueQ(hE^C?DCJf0La+NJfUkE2fa5A7CFX?dCc*I=molD>4BH)m-Sw`GlN+$#m$@q=+&WmMB`S5 zZgzjqBYZ2O9?#mCxL~1#eyNLbTsc*OAZ-9??`@RrN&%d2SAIJ$=H{MMxUQPUiy0aF z#CbLO^Aj1e>prbH_Hul%0d30VBZtBQlEVaF9UB)pBlcxm6MqTW`7@I7Z3|25k^1y!>a84By@V;pfcd_lrO$tq*)*p_j~(rFbK=6X8y~6EN5pJ#J{an^ zan6^A*sJhuYL1(pM%N0}{NM%N#jx()Fb_=ohIdLMT@5%HgJT&gKOEr`CpYIws_&zX54`Z;=HSN)U_PyH65tijkmZokr-{T$y*%3X*^QUYgZ5 zrLkA^w1m~$Y#aNT(XsGEIhI}Y z$(skV<} zJG#l6&~~=(>V+7q~yO z+vH7}9T%;|tiP$zykNkaW9W!tkPRnf61#7wJ+TUDpl)pTDWU^!%f;O0o$>2cmWSxh z-7)6$pL{qa$_rEF_>7Sl_%){{mHDWSAfEzzZ&2EXXPMx#&U?dNO9NqhZ>QVBc?-$m zaQo9CexNX4D~{-Zp!Koj!6~`H8MPKb@}5LYAn#0o_5X@CK(oOyRV+iDB=2c7$(sHW zo0!CU6>9@)U zM$`RccpSf&JMt<*qywN+=2CWZ&38xBOX_+f!gFzqLUE4W{}*Qc{Z+?L*-T#uRohE| zb7~nlOok^X$cf(lb7{kgzNB%aj59YY#%FYHz~02{rj z&GAM{R^9IV@Z`b0hQ3=3PtxYBN%?~`pAoi>dTVk*l1@v~yWtvK+wGjci@GZumxa%0 zQ=;|&*+Y!qs~3xqUqwSb@zAkS?c!RdS>}#K?mm-sauUf?Cy>dit9IBss`HMH|CgnO%;|JRXyd~*%er9)g61Bg!Qp)I zOIaS34auB(0}F;*Nlx2s4rw>Mq_BC%Z}xczKr>UHfFYhGLX z3QoX|fw~MX{|VUNx|pcf$bv5-dDNNsFAO&OUCtBR{dkga9zvZc;hQx1LFGNFUB0y~ z(??I`=sfW`jVYuh4C+S4T8(~#U(363sda)}k+O18Rjf?p#Hh`6NwMaBTh8%d&;|Jj zQ~f}<@q?b!(pf0GZ{w*Swv|3>*hKCnXZmqb0^IXfkB`I3qw6|0F* z_!>73Cp-3>CF_W4Z0B2CRNMP;(5?D!eHIRCSeAzk7Cff&%80qnijXeLEM6LR*movb zwGn!Q(h!Zib+RoDoTvstnV;vU!M~3dyT;6qoSlH7l)qdM=BU^@$pAA3m^%UcA-fzE zjkx(~%?3TuPdjx-APsFbX2|W_l$mHK|DRHa!>JyOj2)r&GojH!p*#0r2eK5Mk3|x` z2!lD22VDgAth=4p^|{sd=*)}*{ac>m+I9ad?S<5H!n_E@FOB>@w!EEg0U)JZ$L#tt zr+b`JVU6s)rK!(e5}FxTgb?|c88Wm$@voHRS`W}oqiTMdP_ximH|k$Py6cXPs>_nR zchAT5hU+WS9ii;hnjzVCtzK^Kj9EoW)En8J==F{hWdQ#yuc{vp=i){nAMwKB{@(oO z(GzC*nUmAS-UV%WC`YLAANBkvuOE(mA+Wbs3Z&&r_!>buJy8Y!xkU1!S>e;t!Mhz0sEhBOjp8&! z7*r&z>hF=K#TVZc#7oUjW~v|6De|wq1Hotv;n(}()SkK0KFRybLkylTfxQ@^T!yJm z`I#%m8?} zIVr4w+c~@i#CK)+frpLfrDUd&q_2_N+!?pC{Y9scr@m$#KrjkJosXfkzZQ)8v=y(% z$Bm)f{zrT0Eo#8qut!D{Wi+AP=8A|!K&72d9rngt;d1l>Y3*bji++SsY&c=jp;$N6 zWKAuWhvW~&zE0V695R*ueuZlz*~>`oK1RtVE=k-?H|oZP@ZAoG0Q6x{i;TB6Mv@mH zupASny=%waJe0A&YL*-hc<0&R7EH3~_x;&BbZ>5)!T@_oz9mPqkD|Q=T-0A~{1O_= zOixL*3OWxsk(=2>L3nNff<_B{4{NZBKgvVkGvYzp_r`d{>@z^N68g7%g6;_5_ED04 z38)N_-g~B8SdrLi3XnYN+_x_3M-3U*9_Rp>HgX3m4o;rl6H+=~tlVS1g3rfPE?Uoj`4V#j2%Ka22`vNeY zojtdk-afg^vsazU`SnDq0ZJAX70xSA-utm3M_K!IzF+&c?(Xh{E^H(`aUm&{J1d((AbK`D*5 zuS~!zSPGfM&1{cQT0K8)zVIRuCR^pc|AHl0EeM1ezA4Ocn|tieMN!_k<7&Hrt&CD< zZp2u9Dplw>zASVy!9j-?NN(ALM@P-n4n0ccm^`WrU-8Cf#Qa#QYC;Njv-KJ=O+N)e zrzef2?Yc8JuAOw?;NNtRgw?rLFfghgfM?_zg?zHjSJHAXYJ9`qJ)-^Wm4#1C?&aZh zWBQCDp8ZMVZpJ`>9>ySt7g$Oa!~}hp-g4`y=G5CY^|NoU6O}ad?tq(PkZYX)g{(GT z=%$l%9C;ti7oQc#bp}DlLTnfAibT)Sec;mg{Uu>(j39-V<@(EL4TG&AP5lz zQryC-hzBHvnCnEH0WGEn82sC?FGoV za9Q?#)Q4iI9qSg!< zc6Ghvv!>sQ7kK}BgJ*RNKo%=X!t|~nW z%bynXmLsC0xqU5t?1GycFre2^e#T1oRMmrY{OsKDb7WH3YG~+kOW$q@t9|Ga(fC1D zqUV(6Ug#~8+HYYD^-O@MnW6Kp;22qKn?{nkgXKP4oJDpzbn2oXcUn-G^JEF?*)6jK z49l?haHsA#I?8vhUx0K5b^R*UO;mT`oCz!^L82MC9jA!pUcH|)CZ4)N?K{$VIGIUM z1xG+}(_O7p^jiMH+cf;hn!I#hFhKLQ1sfiguC@4RTry#Q>o(6ZGflm0vtC`3_wJI) z#yP*57%fk0PU#z|NOLxQA+W`od`59&`k(XHQ4x_ToEpWPD3Bo>Yej1KafKc=bSf<{ zV&Nb;pr|C=dtO-@F1^)bwUhe}`_?t)!QoBokg^$z)3^!xG`HJ8kyms{pQkTZI>u%}b7ZYjsLZhR zqbezB$o*BQi$K)^#DrctGs8;wSl*7aywlvSC%pFky)ag>Vy zo2Tjq!jPmN@kU>3oQ0OKC%QhQuP&WvU8C&nhwmo^#AcV+8dNS+m-^tObfRKA%8tWl zD$YAkeB&B-FKU?6sI4v|j6Fn{tV%LkRGgtBGvC234aiug{>XENw;a*nvYp2RTZ?zqmTrq6Mk&8{@gj|=rG)g9s+j4lpAOy3$qhhh z5}Z%R63vlA5g}$pg@G&jChC3~+=9&Y!wJTJni$niIB5CzNAQERlYX^T_)C@swdY~S z798lsEIar$^`1wJy~>_jopQGIIA}9V=5L#3>S;_DOpKk;`jJFS$k(;m1@VaBS`$u> z5!hg{WpHnxiCdsqb<(RQBz0 zKlw5FZ~5oiI+;;=a3#z8%^n_FPVYcib&T5M^AGwD4rrebq|Lk3nz_Mq+{2c12Og=y z2}k;SIVavp>q5>7O>GwS}GeD^t(={{z%I?==7b literal 0 HcmV?d00001 diff --git a/ydoc/images/manifest.json b/ydoc/images/manifest.json new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/ydoc/images/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/ydoc/images/mstile-150x150.png b/ydoc/images/mstile-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..87b99606766040e8367542c56046440a51d888b2 GIT binary patch literal 8361 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 literal 0 HcmV?d00001 diff --git a/ydoc/images/safari-pinned-tab.svg b/ydoc/images/safari-pinned-tab.svg new file mode 100644 index 0000000..fed47b7 --- /dev/null +++ b/ydoc/images/safari-pinned-tab.svg @@ -0,0 +1,70 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + diff --git a/ydoc/scripts/app.js b/ydoc/scripts/app.js new file mode 100644 index 0000000..e1e4fa1 --- /dev/null +++ b/ydoc/scripts/app.js @@ -0,0 +1,87 @@ +var $panel = document.getElementById('js-panel'), + $header = document.getElementById('js-header'), + $content = document.getElementById('js-content'), + $navIcon = document.getElementById('js-nav-btn'), + $summaryItems = Array.prototype.slice.call(document.querySelectorAll('#js-menu .href')), + $menu = document.getElementById('js-menu'), + $menuContent = document.getElementById('js-menu-content'), + $menuBar = document.getElementById('js-summary-switch'), + navigation; + +var utils = { + debounce: function(func, wait) { + var timeout; + return function () { + clearTimeout(timeout); + timeout = setTimeout(func, wait); + }; + } +}; + +// Add 'active' to summary item +function itemAddActive() { + var locationHref = window.location.href; + $summaryItems.map(function (item, index) { + if (item.href === locationHref) { + // add 'active' for present summary item. + item.parentElement.classList.add('active'); + } else { + item.parentElement.classList.remove('active'); + } + }); +} + +// Add EventListener +function addEvents() { + $panel.addEventListener('click', function (e) { + itemAddActive(); + if (e.target.scrollTop > 0) { + $header.classList.add('moved'); + } else { + $header.classList.remove('moved'); + } + }); + if ($menuContent) { + $menuContent.addEventListener('click', function (e) { + $menu.classList.remove('active'); + setTimeout(itemAddActive, 0); + }); + } + if ($menuBar) { + $menuBar.addEventListener('click', function () { + $menu.classList.toggle('active'); + // 侧栏菜单点击时收起 nav 导航 + if ($navIcon.classList.value.indexOf('active') !== -1) { + navigation.toggle(); + } + }); + } + if ($menu) { + $menu.addEventListener('scroll', function(e) { + sessionStorage.setItem('menuScrollTop', e.target.scrollTop); + }); + } + if ($content) { + $content.addEventListener('scroll', function (e) { + sessionStorage.setItem('contentScrollTop', e.target.scrollTop); + }); + } +} + +// initial components +function initComponents() { + // nav + navigation = responsiveNav('.js-nav', { + customToggle: '#js-nav-btn', + open: function() { + if ($menu) $menu.classList.remove('active'); + setTimeout(itemAddActive, 0); + } + }); +} + + +initComponents(); +addEvents(); +itemAddActive(); + diff --git a/ydoc/scripts/plugins/dollar.min.js b/ydoc/scripts/plugins/dollar.min.js new file mode 100644 index 0000000..9edc48c --- /dev/null +++ b/ydoc/scripts/plugins/dollar.min.js @@ -0,0 +1,6 @@ +/*! + * DollarJS 2.0.0 -- a light, fast, modular, jQuery replacement + * Github: https://github.com/seebigs/dollar-js + * Released under the MIT license: https://opensource.org/licenses/MIT + */ +(function () { function t() { var t, n, e, i = M(); return B.each(W.call(arguments), function (r) { if (r) for (t = 0, n = r.length; t < n; t++)e = r[t], -1 === i.indexOf(e) && i.push(e) }), i } function n(t, n) { if (n) { var e = u(n), i = []; return B.each(e, function (n) { B.each(t, function (t) { typeof n.contains === $ && n.contains(t) && i.push(t) }) }), i } return t } function e(t, e) { function r() { var t = R.readyState; return "interactive" === t || "complete" === t } if (typeof t === H) return i(t, e); if (t.isDollar) return n(t.get(), e); if (t.nodeType) return n([t], e); if (t === t.window) return [t]; if (t.length) { for (var o, f = [], s = 0, u = t.length; s < u; s++)o = t[s], B.isElement(o) && f.push(o); return n(f, e) } return typeof t === $ && (r() ? t() : I.addEventListener ? R.addEventListener("DOMContentLoaded", t, !1) : R.attachEvent("onreadystatechange", function () { r() && t() })), [] } function i(t, n) { if (n) { var e = []; if (n = u(n), n.length > 1) { for (var r = 0, o = n.length; r < o; r++)B.merge(e, i(t, n[r])); return e } n = n[0] } else n = U; if (!n) return []; var f = /^\s*(?:#([\w-]+)|(\w+)|\.([\w-]+)|(<[\w\W]+>)[^>]*)\s*$/.exec(t); if (f) { if (t = f[1]) { var a = R.getElementById(t); return a && n !== a && n.contains(a) ? [a] : [] } if (t = f[2]) return n.getElementsByTagName(t); if (t = f[3]) return n.getElementsByClassName(t); if (t = f[4]) return [s(t)] } else { var l = /(.*)\:(.+)/.exec(t); if (l) { var c = l[1] || "*", h = l[2].split("("), p = _[h[0]]; if (p) return p(c, n, h) } } return W.call(n.querySelectorAll(t)) } function r(t) { var n = i(t); return -1 !== P.indexOf.call(n, this) } function o(t, n, e) { return !(!n || !t || 1 !== t.nodeType) && (typeof n !== H ? n.nodeType ? t === n : typeof n === $ ? !!n.call(t, t, e) : !!n.length && -1 !== n.indexOf(t) : G.call(t, n)) } function f(t, n) { for (var e = [], i = 0, r = t.length; i < r; i++)o(t[i], n, i) && e.push(t[i]); return e } function s(t) { var n = /^<(\w+)\s*\/?>(?:<\/\1>|)$/.exec(t); if (n) return R.createElement(n[1]); var e = R.createElement("div"); return e.innerHTML = t, e.childNodes[0] } function u(t) { return typeof t === H ? i(t) : t.isDollar ? t.get() : 1 === t.nodeType || 9 === t.nodeType ? [t] : Array.isArray(t) ? t : [U] } function a(t) { var n = t && t.nodeType; return n && 3 !== n && 8 !== n && 2 !== n } function l(t) { return 9 === t.nodeType && (t = t.documentElement), a(t) ? t : q } function c(t, n) { if (t) return t === t.window ? t[n] : (t = l(t), t && t.hasAttribute(n) ? t.getAttribute(n) : q) } function h(t, n, e) { return t === t.window && (t[n] = e), (t = l(t)) && t.setAttribute(n, e) } function p(t, n) { return t === t.window && (t[n] = q), (t = l(t)) && t.removeAttribute(n) } function d(t) { return Number(c(t, J)) || q } function v(t, n) { return h(t, J, n) } function y(t, n, e) { var i = d(n); if (i) return e ? t[i] && t[i][e] : t[i] } function m(t, n, e, i) { var r = d(n); r || (r = K, v(n, r), K++), t[r] || (t[r] = {}), t[r][e] = i } function g(t, n, e, i) { var r = y(t, n, e) || []; r.push(i), m(t, n, e, r) } function b(t, n, e) { var i = n + " " + C(e), r = t.style.transition ? t.style.transition.split(/,\s?/) : [], o = !1; return r.forEach(function (t, e) { 0 === t.indexOf(n + " ") && (r[e] = i, o = !0) }), o || r.push(i), r.join(", ") } function E(t, n) { var e = t.style.transition.split(/,\s?/), i = []; return e.forEach(function (t) { 0 !== t.indexOf(n + " ") && i.push(t) }), i.join(", ") } function C(t) { var n = []; return n.push(typeof t.duration === H ? t.duration : (parseInt(t.duration) || 400) + "ms"), n.push(t.easing || "ease"), n.push(typeof t.delay === H ? t.delay : (parseInt(t.delay) || 0) + "ms"), n.join(" ") } function T(t, n) { function e(t) { u.appendChild(t), o = !0 } t = [].concat.apply([], t); var i, r, o, f, u, a, l = t.length, c = this.length; for (r = 0; r < c; r++) { for (o = !1, u = R.createDocumentFragment(), i = 0; i < l; i++)(f = t[i]) && (typeof f === H ? (a = s(f)) && e(a) : 1 === f.nodeType ? e(f) : f.isDollar ? f.each(e) : typeof f === $ && (a = f(this[r], r), typeof a === H && (a = s(a)), a && e(a))); o && n(this[r], u) } return this } function w(t) { return t && t.dataset || function () { var n, e = {}, i = t.attributes, r = /^data-[a-z_\-\d]*$/i; for (var o in i) i.hasOwnProperty(o) && (n = i[o].name, r.test(n) && (n = B.format.dashToCamel(n.substr(5)), e[n] = i[o].value)); return e }() } function N(t, n) { return a(t) ? t[n] : q } function x(t, n) { return t && typeof n === H ? t.ownerDocument.defaultView.opener ? t.ownerDocument.defaultView.getComputedStyle(t, null)[n] || "" : k.getComputedStyle(t, null)[n] || t.style[n] || "" : "" } function O(t, n) { var e; return n = typeof n === H ? n : "", t ? (e = "float" === n ? "styleFloat" : B.format.dashToCamel(n.replace(/^-ms-/, "ms-")), t.currentStyle[e]) : "" } function S(t) { var n = t.style.display; if (n && "none" !== n || (n = y(X, t, "nonHiddenDisplayValue") || ""), !n && t.parentNode) { var e = R.createElement(t.nodeName); t.parentNode.appendChild(e), n = Y(e, "display"), t.parentNode.removeChild(e), m(X, t, "nonHiddenDisplayValue", n) } return n } function A(t, n) { if (typeof t !== H || typeof n !== $) return this; t = t.split(" "); var e, i, r; return this.each(function () { for (e = this.addEventListener || this.attachEvent, i = 0, r = t.length; i < r; i++)e.call(this, t[i], n, !1), g(X, this, tt, n) }), this } function D(t, n) { if (typeof t !== H) return this; t = t.split(" "); var e, i, r, o, f; return this.each(function () { for (e = 0, i = t.length; e < i; e++)for (r = typeof n === $ ? [n] : y(X, this, tt) || [], o = 0, f = r.length; o < f; o++)this.removeEventListener ? this.removeEventListener(t[e], r[o], !1) : this.detachEvent(t[e], r[o], !1) }), this } function j(t, n, e) { var i, r = { bubbles: !0, cancelable: !0 }; e && e.length && (r.detail = e), B.each(n, function (n) { B.each(t, function (t) { "click" === n ? t.click() : "focus" === n ? t.focus() : "blur" === n ? t.blur() : (i = new k.CustomEvent(n, r), t.dispatchEvent(i)) }) }) } function L(t, n) { return typeof n === $ ? A.call(this, t, n) : (j(this, t.split(" ")), this) } var q, B, M = function (t, n) { return new M.fn.init(t, n) }, H = "string", $ = "function", k = window, I = k.Element.prototype, z = Object.prototype, V = z.toString, F = z.hasOwnProperty, P = Array.prototype, W = P.slice, R = document, U = document.documentElement, Z = /[\s\t\r\n\f]+/g; M.isDollar = !0, M.fn = { isDollar: !0, indexOf: P.indexOf, push: P.push, pop: P.pop, shift: P.shift, unshift: P.unshift, slice: P.slice, splice: P.splice }, M.fn.init = function (t, n) { if (this.length = 0, !t) return this; if (!n) { if (/^#[\w-]+$/.test(t)) { var i = R.getElementById(t.substr(1)); return i && (this[0] = i, this.length = 1), this } if (/^[a-z]+$/.test(t)) { for (var r = R.getElementsByTagName(t), o = r.length, f = 0; f < o; f++)this[f] = r[f]; return this.length = o, this } } return B.merge(this, e(t, n)) }, M.fn.init.prototype = M.fn; var _ = { contains: function (t, n, e) { var r = e[1] && e[1].replace(/[\"\'\)]/g, ""); return r ? f(i(t, n), function (t) { return -1 !== (t.textContent || t.innerText).indexOf(r) }) : [] }, hidden: function (t, n) { return f(i(t, n), function (t) { return 1 === t.nodeType && !(t.offsetWidth || t.offsetHeight || t.getClientRects().length) }) }, visible: function (t, n) { return f(i(t, n), function (t) { return 1 === t.nodeType && !!(t.offsetWidth || t.offsetHeight || t.getClientRects().length) }) }, even: function (t, n) { return W.call(n.querySelectorAll(t + ":nth-child(even)")) }, odd: function (t, n) { return W.call(n.querySelectorAll(t + ":nth-child(odd)")) }, has: function (t, n, i) { var r = typeof i[1] === H && i[1].replace(")", ""); return r ? f(e(t, n), function (t) { return 1 === t.nodeType && !!e(r, t).length }) : [] }, not: function (t, n, e) { n === U && (n = R); var r = t; "*" !== t && " " === t[t.length - 1] && (r = t + "*"); var o = e[1].split(")"), f = o[1], s = (o[0] || "").split(","); f && (r += f); var u = [], a = i(r, n); return B.each(a, function (t) { var n = !0; B.each(s, function (e) { if (G.call(t, e)) return n = !1, !1 }), n && u.push(t) }), u } }, G = I.matches || I.webkitMatchesSelector || I.mozMatchesSelector || I.msMatchesSelector || I.oMatchesSelector || r, J = "dollar-node-id", K = 1, Q = {}, X = {}; M.utils = B = function () { function t(t) { return !!t && (1 === t.nodeType || 9 === t.nodeType) } function n(t) { return V.call(t) === o + "Object]" } function e(t, n) { if (t) if (t.length !== q) { for (var e = 0, i = t.length; e < i; e++)if (!1 === n.call(t[e], t[e], e, t)) return } else for (var r in t) if (F.call(t, r) && !1 === n.call(t[r], t[r], r, t)) return } function i() { var t = arguments[0], n = function (n, e) { n !== q && (t[e] = n) }; return e(W.call(arguments, 1), function (t) { e(t, n) }), t } function r() { var t, n, i = arguments[0]; return e(W.call(arguments, 1), function (e) { if (e) for (t = 0, n = e.length; t < n; t++)-1 === i.indexOf(e[t]) && i.push(e[t]) }), i } var o = "[object "; return { isElement: t, isObject: n, each: e, extend: i, merge: r, format: { camelToDash: function (t) { return t.replace(/([A-Z])/g, "-$1").toLowerCase() }, dashToCamel: function (t) { return t.replace(/\-(.)/g, function (t, n) { return n.charAt(0).toUpperCase() }) } } } }(), M.fn.closest = function (n, i) { if (!n) return M(); for (var r, o = e(n, i), f = [], s = 0, u = this.length; s < u; s++)for (r = this[s]; r && r !== i;) { if (-1 !== P.indexOf.call(o, r)) { f.push(r); break } r = r.parentNode } return t(f) }, M.fn.each = M.fn.forEach = function (t) { return B.each(this, t), this }, M.fn.eq = function (t) { return t = Array.isArray(t) ? NaN : parseInt(t, 10), M(t >= 0 ? this[t] : this[this.length + t]) }, M.fn.filter = function (n) { return this.length && n ? t(f(this, n)) : M() }, M.fn.find = function (n) { return n && this.length ? t(e(n, this)) : M() }, M.fn.get = function (t) { return t === q ? W.call(this, 0) : t < 0 ? this[t + this.length] : this[t] }, M.fn.reverse = P.reverse, M.fn.animate = function (t, n, e) { B.isObject(n) || (n = { duration: n }); var i = "transitionend"; return this.each(function (r, o) { B.each(t, function (t, f) { r.style.transition = b(r, f, n), r.style[f] = t; var s = function (t) { r.removeEventListener(i, s, !0), r.style.transition = E(r, t), typeof e === $ && e.call(r, r, o) }; r.addEventListener(i, s, !0) }) }), this }, M.fn.fadeIn = function (t, n) { return this.animate({ opacity: 1 }, t, n) }, M.fn.fadeOut = function (t, n) { return this.animate({ opacity: 0 }, t, n) }, M.fn.add = function (n, e) { return n ? t(this, M(n, e)) : this }, M.fn.concat = function () { var n = W.call(arguments); return n.unshift(this), t.apply(this, n) }, M.fn.has = function (t) { return t ? this.filter(function () { return !!e(t, this).length }) : M() }, M.fn.is = function (t) { return !(!t || !this.filter(t).length) }, M.fn.map = function (n) { if (typeof n !== $) return this; for (var e, i = [], r = 0, o = this.length; r < o; r++) { if (e = n.call(this[r], this[r], r, this), !B.isElement(e)) throw new Error(".map fn should return an Element, not " + typeof e); i.push(e) } return t.call(this, i) }, M.fn.not = function (n) { if (!n) return this; var e; return e = typeof n === $ ? function (t, e) { return !n.call(t, e, t) } : function (t, e) { return !o(t, n, e) }, t(this.filter(e)) }, M.fn.after = function () { return T.call(this, arguments, function (t, n) { var e = t.parentNode; e && (t.nextSibling ? e.insertBefore(n, t.nextSibling) : e.appendChild(n)) }) }, M.fn.append = function () { return T.call(this, arguments, function (t, n) { t.appendChild(n) }) }, M.fn.before = function () { return T.call(this, arguments, function (t, n) { t.parentNode && t.parentNode.insertBefore(n, t) }) }, M.fn.clone = function () { var t, n, e = this.length; for (n = 0; n < e; n++)t = this[n], 1 !== t.nodeType && 11 !== t.nodeType || (this[n] = t.cloneNode(!0)); return this }, M.fn.empty = function () { var t, n, e = this.length; for (n = 0; n < e; n++)t = this[n], 1 === t.nodeType && (t.textContent = ""); return this }, M.fn.html = function (t) { var n, e, i = this.length, r = this[0]; if (t === q) return r && 1 === r.nodeType ? r.innerHTML : q; try { for (e = 0; e < i; e++)n = this[e], 1 === n.nodeType && (n.innerHTML = t) } catch (n) { this.empty().append(t) } return this }, M.fn.prepend = function () { return T.call(this, arguments, function (t, n) { t.firstChild ? t.insertBefore(n, t.firstChild) : t.appendChild(n) }) }, M.fn.remove = function (t) { var n, e, i = this.length; if (t === q) for (e = 0; e < i; e++)n = this[e], n.parentNode && n.parentNode.removeChild(n); else for (e = 0; e < i; e++)n = this[e], o(n, t, e) && n.parentNode && n.parentNode.removeChild(n); return this }, M.fn.attr = function (t, n) { return n === q ? c(this[0], t) : (this.each(function (e, i) { a(e) && (typeof n === $ && (n = n(c(e, t), i)), e.setAttribute(t, n)) }), this) }, M.fn.data = function (t, n) { function e(t, n) { m(Q, i, n, t) } if (!this.length) return q; var i, r = {}; if (!t) return B.extend({}, w(this[0]), y(Q, this[0])); if (typeof t === H) { if (n === q) { var o = y(Q, this[0], t); return o === q ? w(this[0])[t] : o } r[t] = n } else B.isObject(t) && (r = t); for (var f = 0, s = this.length; f < s; f++)i = this[f], B.each(r, e); return this }, M.fn.prop = function (t, n) { return n === q ? N(this[0], t) : (this.each(function (e, i) { a(e) && (typeof n === $ && (n = n(N(e, t), i)), e[t] = n) }), this) }, M.fn.removeAttr = function (t) { return this.each(function () { this.removeAttribute(t) }), this }, M.fn.removeData = function (t) { for (var n, e, i = 0, r = this.length; i < r; i++)n = this[i], e = d(n), t ? (e && delete Q[e][t], n && (n.dataset ? n.dataset[t] && delete n.dataset[t] : p(n, "data-" + B.format.camelToDash(t)))) : Q[e] = {}; return this }, M.fn.removeProp = function (t) { return this.each(function () { a(this) && delete this[t] }), this }, M.fn.text = function (t) { if (t !== q) return this.each(function (n, e) { 1 !== n.nodeType && 11 !== n.nodeType && 9 !== n.nodeType || (n.textContent = typeof t === $ ? t(n.textContent, e) : t) }), this; var n = ""; return this.each(function (t) { var e = t.nodeType; 1 === e || 9 === e || 11 === e ? typeof t.textContent === H && (n += t.textContent) : 3 !== e && 4 !== e || (n += t.nodeValue) }), n }, M.fn.val = function (t) { if (t === q) return this[0] ? this[0].value : q; for (var n = 0; n < this.length && 1 === this[n].nodeType; n++)typeof t === $ && (t = t.call(this[n], this[n].value, n)), this[n].value = t; return this }; var Y = k.getComputedStyle !== q ? x : O; M.fn.addClass = function (t) { if (!t) return this; var n, e, i, r = this.length; if (typeof t === H) for (n = t.trim().split(" "), i = 0; i < r; i++)e = this[i].className.trim().replace(Z, " ").split(" "), this[i].className = B.merge([], e, n).join(" "); else if (typeof t === $) for (i = 0; i < r; i++)n = t.call(this[i], this[i].className, i).split(" "), e = this[i].className.trim().replace(Z, " ").split(" "), this[i].className = B.merge([], e, n).join(" "); return this }, M.fn.css = function (t, n) { function e(t, n) { r.style[n] = typeof t === $ ? t.call(r, Y(r, n), i) : t } if (!t) return this; var i, r, o = this.length, f = {}; if (typeof t === H) { if (n === q) return Y(this[0], t); f[t] = n } else B.isObject(t) && (f = t); for (i = 0; i < o; i++)r = this[i], B.each(f, e); return this }, M.fn.hasClass = function (t) { if (!t) return !1; t = " " + t.trim() + " "; for (var n = 0, e = this.length; n < e; n++)if (1 === this[n].nodeType && -1 !== (" " + this[n].className + " ").replace(Z, " ").indexOf(t)) return !0; return !1 }, M.fn.height = function () { return parseFloat(this.eq(0).css("height")) || 0 }, M.fn.hide = function () { return this.each(function () { this.style.display = "none" }), this }, M.fn.removeClass = function (t) { function n(t) { -1 === o.indexOf(t) && i.push(t) } for (var e, i, r, o, f = 0, s = this.length; f < s; f++)e = this[f], i = [], t ? (o = typeof t === $ ? t.call(e, e.className, f) : t, o.length && (o = typeof o === H ? o.trim().split(" ") : o, r = e.className.replace(Z, " ").split(" "), B.each(r, n), e.className = i.join(" "))) : e.className = ""; return this }, M.fn.show = function () { return this.each(function () { this.style.display = S(this), this.style.visibility = "visible", this.style.opacity = 1 }), this }, M.fn.width = function () { return parseFloat(this.eq(0).css("width")) || 0 }, M.fn.children = function (n) { for (var e = [], i = 0, r = this.length; i < r; i++)B.merge(e, this[i].children); return t(n ? M.fn.filter.call(e, n) : e) }, M.fn.first = function () { return this.eq(0) }, M.fn.last = function () { return this.eq(-1) }, M.fn.next = function (n) { for (var e, i = [], r = 0, o = this.length; r < o; r++)(e = this[r].nextElementSibling) && i.push(e); return t(n ? M.fn.filter.call(i, n) : i) }, M.fn.parent = function (n) { for (var e, i = [], r = 0, o = this.length; r < o; r++)(e = this[r].parentNode) && i.push(e); return t(n ? M.fn.filter.call(i, n) : i) }, M.fn.prev = function (n) { for (var e, i = [], r = 0, o = this.length; r < o; r++)(e = this[r].previousElementSibling) && i.push(e); return t(n ? M.fn.filter.call(i, n) : i) }, M.fn.siblings = function (n) { for (var e, i = [], r = 0, o = this.length; r < o; r++)for (this[r].parentNode && (e = this[r].parentNode.firstChild); e;)e !== this[r] && 1 === e.nodeType && i.push(e), e = e.nextSibling; return t(n ? M.fn.filter.call(i, n) : i) }; var tt = "activeEventListeners"; M.fn.click = function (t) { return L.call(this, "click", t) }, M.fn.focus = function (t) { return L.call(this, "focus", t) }, M.fn.blur = function (t) { return L.call(this, "blur", t) }, M.fn.change = function (t) { return L.call(this, "change", t) }, M.fn.resize = function (t) { return L.call(this, "resize", t) }, M.fn.off = M.fn.unbind = D, M.fn.on = M.fn.bind = A, M.fn.trigger = function (t) { return typeof t !== H ? this : (t = t.split(" "), j(this, t, W.call(arguments, 1)), this) }, function (t) { function n(t, n) { n = n || { bubbles: !1, cancelable: !1 }; var e = document.createEvent("CustomEvent"); return e.initCustomEvent(t, n.bubbles, n.cancelable, n.detail), e } typeof t.CustomEvent !== $ && (n.prototype = t.Event.prototype, t.CustomEvent = n) }(k), function () { var t = window; "undefined" != typeof module && module.exports ? module.exports = M : typeof t.define === $ && t.define.amd ? t.define(function () { return M }) : t.$ = M }.call(this) }).call(this); \ No newline at end of file diff --git a/ydoc/scripts/plugins/responsive-nav.min.js b/ydoc/scripts/plugins/responsive-nav.min.js new file mode 100644 index 0000000..add03c1 --- /dev/null +++ b/ydoc/scripts/plugins/responsive-nav.min.js @@ -0,0 +1 @@ +!function (a, b, c) { "use strict"; var d = function (d, e) { var f = !!b.getComputedStyle; f || (b.getComputedStyle = function (a) { return this.el = a, this.getPropertyValue = function (b) { var c = /(\-([a-z]){1})/g; return "float" === b && (b = "styleFloat"), c.test(b) && (b = b.replace(c, function () { return arguments[2].toUpperCase() })), a.currentStyle[b] ? a.currentStyle[b] : null }, this }); var g, h, i, j, k, l, m = function (a, b, c, d) { if ("addEventListener" in a) try { a.addEventListener(b, c, d) } catch (e) { if ("object" != typeof c || !c.handleEvent) throw e; a.addEventListener(b, function (a) { c.handleEvent.call(c, a) }, d) } else "attachEvent" in a && ("object" == typeof c && c.handleEvent ? a.attachEvent("on" + b, function () { c.handleEvent.call(c) }) : a.attachEvent("on" + b, c)) }, n = function (a, b, c, d) { if ("removeEventListener" in a) try { a.removeEventListener(b, c, d) } catch (e) { if ("object" != typeof c || !c.handleEvent) throw e; a.removeEventListener(b, function (a) { c.handleEvent.call(c, a) }, d) } else "detachEvent" in a && ("object" == typeof c && c.handleEvent ? a.detachEvent("on" + b, function () { c.handleEvent.call(c) }) : a.detachEvent("on" + b, c)) }, o = function (a) { if (a.children.length < 1) throw new Error("The Nav container has no containing elements"); for (var b = [], c = 0; c < a.children.length; c++)1 === a.children[c].nodeType && b.push(a.children[c]); return b }, p = function (a, b) { for (var c in b) a.setAttribute(c, b[c]) }, q = function (a, b) { 0 !== a.className.indexOf(b) && (a.className += " " + b, a.className = a.className.replace(/(^\s*)|(\s*$)/g, "")) }, r = function (a, b) { var c = new RegExp("(\\s|^)" + b + "(\\s|$)"); a.className = a.className.replace(c, " ").replace(/(^\s*)|(\s*$)/g, "") }, s = function (a, b, c) { for (var d = 0; d < a.length; d++)b.call(c, d, a[d]) }, t = a.createElement("style"), u = a.documentElement, v = function (b, c) { var d; this.options = { animate: !0, transition: 284, label: "Menu", insert: "before", customToggle: "", closeOnNavClick: !1, openPos: "relative", navClass: "js-nav", navActiveClass: "js-nav-active", jsClass: "js", init: function () { }, open: function () { }, close: function () { } }; for (d in c) this.options[d] = c[d]; if (q(u, this.options.jsClass), this.wrapperEl = b.replace("#", ""), a.getElementById(this.wrapperEl)) this.wrapper = a.getElementById(this.wrapperEl); else { if (!a.querySelector(this.wrapperEl)) throw new Error("The nav element you are trying to select doesn't exist"); this.wrapper = a.querySelector(this.wrapperEl) } this.wrapper.inner = o(this.wrapper), h = this.options, g = this.wrapper, this._init(this) }; return v.prototype = { destroy: function () { this._removeStyles(), r(g, "closed"), r(g, "opened"), r(g, h.navClass), r(g, h.navClass + "-" + this.index), r(u, h.navActiveClass), g.removeAttribute("style"), g.removeAttribute("aria-hidden"), n(b, "resize", this, !1), n(b, "focus", this, !1), n(a.body, "touchmove", this, !1), n(i, "touchstart", this, !1), n(i, "touchend", this, !1), n(i, "mouseup", this, !1), n(i, "keyup", this, !1), n(i, "click", this, !1), h.customToggle ? i.removeAttribute("aria-hidden") : i.parentNode.removeChild(i) }, toggle: function () { j === !0 && (l ? this.close() : this.open()) }, open: function () { l || (r(g, "closed"), q(g, "opened"), q(u, h.navActiveClass), q(i, "active"), g.style.position = h.openPos, p(g, { "aria-hidden": "false" }), l = !0, h.open()) }, close: function () { l && (q(g, "closed"), r(g, "opened"), r(u, h.navActiveClass), r(i, "active"), p(g, { "aria-hidden": "true" }), h.animate ? (j = !1, setTimeout(function () { g.style.position = "absolute", j = !0 }, h.transition + 10)) : g.style.position = "absolute", l = !1, h.close()) }, resize: function () { "none" !== b.getComputedStyle(i, null).getPropertyValue("display") ? (k = !0, p(i, { "aria-hidden": "false" }), g.className.match(/(^|\s)closed(\s|$)/) && (p(g, { "aria-hidden": "true" }), g.style.position = "absolute"), this._createStyles(), this._calcHeight()) : (k = !1, p(i, { "aria-hidden": "true" }), p(g, { "aria-hidden": "false" }), g.style.position = h.openPos, this._removeStyles()) }, handleEvent: function (a) { var c = a || b.event; switch (c.type) { case "touchstart": this._onTouchStart(c); break; case "touchmove": this._onTouchMove(c); break; case "touchend": case "mouseup": this._onTouchEnd(c); break; case "click": this._preventDefault(c); break; case "keyup": this._onKeyUp(c); break; case "focus": case "resize": this.resize(c) } }, _init: function () { this.index = c++ , q(g, h.navClass), q(g, h.navClass + "-" + this.index), q(g, "closed"), j = !0, l = !1, this._closeOnNavClick(), this._createToggle(), this._transitions(), this.resize(); var d = this; setTimeout(function () { d.resize() }, 20), m(b, "resize", this, !1), m(b, "focus", this, !1), m(a.body, "touchmove", this, !1), m(i, "touchstart", this, !1), m(i, "touchend", this, !1), m(i, "mouseup", this, !1), m(i, "keyup", this, !1), m(i, "click", this, !1), h.init() }, _createStyles: function () { t.parentNode || (t.type = "text/css", a.getElementsByTagName("head")[0].appendChild(t)) }, _removeStyles: function () { t.parentNode && t.parentNode.removeChild(t) }, _createToggle: function () { if (h.customToggle) { var b = h.customToggle.replace("#", ""); if (a.getElementById(b)) i = a.getElementById(b); else { if (!a.querySelector(b)) throw new Error("The custom nav toggle you are trying to select doesn't exist"); i = a.querySelector(b) } } else { var c = a.createElement("a"); c.innerHTML = h.label, p(c, { href: "#", "class": "nav-toggle" }), "after" === h.insert ? g.parentNode.insertBefore(c, g.nextSibling) : g.parentNode.insertBefore(c, g), i = c } }, _closeOnNavClick: function () { if (h.closeOnNavClick) { var a = g.getElementsByTagName("a"), b = this; s(a, function (c) { m(a[c], "click", function () { k && b.toggle() }, !1) }) } }, _preventDefault: function (a) { return a.preventDefault ? (a.stopImmediatePropagation && a.stopImmediatePropagation(), a.preventDefault(), a.stopPropagation(), !1) : void (a.returnValue = !1) }, _onTouchStart: function (a) { Event.prototype.stopImmediatePropagation || this._preventDefault(a), this.startX = a.touches[0].clientX, this.startY = a.touches[0].clientY, this.touchHasMoved = !1, n(i, "mouseup", this, !1) }, _onTouchMove: function (a) { (Math.abs(a.touches[0].clientX - this.startX) > 10 || Math.abs(a.touches[0].clientY - this.startY) > 10) && (this.touchHasMoved = !0) }, _onTouchEnd: function (a) { if (this._preventDefault(a), k && !this.touchHasMoved) { if ("touchend" === a.type) return void this.toggle(); var c = a || b.event; 3 !== c.which && 2 !== c.button && this.toggle() } }, _onKeyUp: function (a) { var c = a || b.event; 13 === c.keyCode && this.toggle() }, _transitions: function () { if (h.animate) { var a = g.style, b = "max-height " + h.transition + "ms"; a.WebkitTransition = a.MozTransition = a.OTransition = a.transition = b } }, _calcHeight: function () { for (var a = 0, b = 0; b < g.inner.length; b++)a += g.inner[b].offsetHeight; var c = "." + h.jsClass + " ." + h.navClass + "-" + this.index + ".opened{max-height:" + a + "px !important} ." + h.jsClass + " ." + h.navClass + "-" + this.index + ".opened.dropdown-active {max-height:9999px !important}"; t.styleSheet ? t.styleSheet.cssText = c : t.innerHTML = c, c = "" } }, new v(d, e) }; "undefined" != typeof module && module.exports ? module.exports = d : b.responsiveNav = d }(document, window, 0); \ No newline at end of file diff --git a/ydoc/scripts/plugins/slideout.min.js b/ydoc/scripts/plugins/slideout.min.js new file mode 100644 index 0000000..81c06b8 --- /dev/null +++ b/ydoc/scripts/plugins/slideout.min.js @@ -0,0 +1 @@ +!function (t) { if ("object" == typeof exports && "undefined" != typeof module) module.exports = t(); else if ("function" == typeof define && define.amd) define([], t); else { var e; "undefined" != typeof window ? e = window : "undefined" != typeof global ? e = global : "undefined" != typeof self && (e = self), e.Slideout = t() } }(function () { var t, e, n; return function i(t, e, n) { function o(r, a) { if (!e[r]) { if (!t[r]) { var u = typeof require == "function" && require; if (!a && u) return u(r, !0); if (s) return s(r, !0); var l = new Error("Cannot find module '" + r + "'"); throw l.code = "MODULE_NOT_FOUND", l } var f = e[r] = { exports: {} }; t[r][0].call(f.exports, function (e) { var n = t[r][1][e]; return o(n ? n : e) }, f, f.exports, i, t, e, n) } return e[r].exports } var s = typeof require == "function" && require; for (var r = 0; r < n.length; r++)o(n[r]); return o }({ 1: [function (t, e, n) { "use strict"; var i = t("decouple"); var o = t("emitter"); var s; var r = false; var a = window.document; var u = a.documentElement; var l = window.navigator.msPointerEnabled; var f = { start: l ? "MSPointerDown" : "touchstart", move: l ? "MSPointerMove" : "touchmove", end: l ? "MSPointerUp" : "touchend" }; var h = function v() { var t = /^(Webkit|Khtml|Moz|ms|O)(?=[A-Z])/; var e = a.getElementsByTagName("script")[0].style; for (var n in e) { if (t.test(n)) { return "-" + n.match(t)[0].toLowerCase() + "-" } } if ("WebkitOpacity" in e) { return "-webkit-" } if ("KhtmlOpacity" in e) { return "-khtml-" } return "" }(); function c(t, e) { for (var n in e) { if (e[n]) { t[n] = e[n] } } return t } function p(t, e) { t.prototype = c(t.prototype || {}, e.prototype) } function d(t) { while (t.parentNode) { if (t.getAttribute("data-slideout-ignore") !== null) { return t } t = t.parentNode } return null } function _(t) { t = t || {}; this._startOffsetX = 0; this._currentOffsetX = 0; this._opening = false; this._moved = false; this._opened = false; this._preventOpen = false; this._touch = t.touch === undefined ? true : t.touch && true; this._side = t.side || "left"; this.panel = t.panel; this.menu = t.menu; if (!this.panel.classList.contains("slideout-panel")) { this.panel.classList.add("slideout-panel") } if (!this.panel.classList.contains("slideout-panel-" + this._side)) { this.panel.classList.add("slideout-panel-" + this._side) } if (!this.menu.classList.contains("slideout-menu")) { this.menu.classList.add("slideout-menu") } if (!this.menu.classList.contains("slideout-menu-" + this._side)) { this.menu.classList.add("slideout-menu-" + this._side) } this._fx = t.fx || "ease"; this._duration = parseInt(t.duration, 10) || 300; this._tolerance = parseInt(t.tolerance, 10) || 70; this._padding = this._translateTo = parseInt(t.padding, 10) || 256; this._orientation = this._side === "right" ? -1 : 1; this._translateTo *= this._orientation; if (this._touch) { this._initTouchEvents() } } p(_, o); _.prototype.open = function () { var t = this; this.emit("beforeopen"); if (!u.classList.contains("slideout-open")) { u.classList.add("slideout-open") } this._setTransition(); this._translateXTo(this._translateTo); this._opened = true; setTimeout(function () { t.panel.style.transition = t.panel.style["-webkit-transition"] = ""; t.emit("open") }, this._duration + 50); return this }; _.prototype.close = function () { var t = this; if (!this.isOpen() && !this._opening) { return this } this.emit("beforeclose"); this._setTransition(); this._translateXTo(0); this._opened = false; setTimeout(function () { u.classList.remove("slideout-open"); t.panel.style.transition = t.panel.style["-webkit-transition"] = t.panel.style[h + "transform"] = t.panel.style.transform = ""; t.emit("close") }, this._duration + 50); return this }; _.prototype.toggle = function () { return this.isOpen() ? this.close() : this.open() }; _.prototype.isOpen = function () { return this._opened }; _.prototype._translateXTo = function (t) { this._currentOffsetX = t; this.panel.style[h + "transform"] = this.panel.style.transform = "translateX(" + t + "px)"; return this }; _.prototype._setTransition = function () { this.panel.style[h + "transition"] = this.panel.style.transition = h + "transform " + this._duration + "ms " + this._fx; return this }; _.prototype._initTouchEvents = function () { var t = this; this._onScrollFn = i(a, "scroll", function () { if (!t._moved) { clearTimeout(s); r = true; s = setTimeout(function () { r = false }, 250) } }); this._preventMove = function (e) { if (t._moved) { e.preventDefault() } }; a.addEventListener(f.move, this._preventMove); this._resetTouchFn = function (e) { if (typeof e.touches === "undefined") { return } t._moved = false; t._opening = false; t._startOffsetX = e.touches[0].pageX; t._preventOpen = !t._touch || !t.isOpen() && t.menu.clientWidth !== 0 }; this.panel.addEventListener(f.start, this._resetTouchFn); this._onTouchCancelFn = function () { t._moved = false; t._opening = false }; this.panel.addEventListener("touchcancel", this._onTouchCancelFn); this._onTouchEndFn = function () { if (t._moved) { t.emit("translateend"); t._opening && Math.abs(t._currentOffsetX) > t._tolerance ? t.open() : t.close() } t._moved = false }; this.panel.addEventListener(f.end, this._onTouchEndFn); this._onTouchMoveFn = function (e) { if (r || t._preventOpen || typeof e.touches === "undefined" || d(e.target)) { return } var n = e.touches[0].clientX - t._startOffsetX; var i = t._currentOffsetX = n; if (Math.abs(i) > t._padding) { return } if (Math.abs(n) > 20) { t._opening = true; var o = n * t._orientation; if (t._opened && o > 0 || !t._opened && o < 0) { return } if (!t._moved) { t.emit("translatestart") } if (o <= 0) { i = n + t._padding * t._orientation; t._opening = false } if (!(t._moved && u.classList.contains("slideout-open"))) { u.classList.add("slideout-open") } t.panel.style[h + "transform"] = t.panel.style.transform = "translateX(" + i + "px)"; t.emit("translate", i); t._moved = true } }; this.panel.addEventListener(f.move, this._onTouchMoveFn); return this }; _.prototype.enableTouch = function () { this._touch = true; return this }; _.prototype.disableTouch = function () { this._touch = false; return this }; _.prototype.destroy = function () { this.close(); a.removeEventListener(f.move, this._preventMove); this.panel.removeEventListener(f.start, this._resetTouchFn); this.panel.removeEventListener("touchcancel", this._onTouchCancelFn); this.panel.removeEventListener(f.end, this._onTouchEndFn); this.panel.removeEventListener(f.move, this._onTouchMoveFn); a.removeEventListener("scroll", this._onScrollFn); this.open = this.close = function () { }; return this }; e.exports = _ }, { decouple: 2, emitter: 3 }], 2: [function (t, e, n) { "use strict"; var i = function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (t) { window.setTimeout(t, 1e3 / 60) } }(); function o(t, e, n) { var o, s = false; function r(t) { o = t; a() } function a() { if (!s) { i(u); s = true } } function u() { n.call(t, o); s = false } t.addEventListener(e, r, false); return r } e.exports = o }, {}], 3: [function (t, e, n) { "use strict"; var i = function (t, e) { if (!(t instanceof e)) { throw new TypeError("Cannot call a class as a function") } }; n.__esModule = true; var o = function () { function t() { i(this, t) } t.prototype.on = function e(t, n) { this._eventCollection = this._eventCollection || {}; this._eventCollection[t] = this._eventCollection[t] || []; this._eventCollection[t].push(n); return this }; t.prototype.once = function n(t, e) { var n = this; function i() { n.off(t, i); e.apply(this, arguments) } i.listener = e; this.on(t, i); return this }; t.prototype.off = function o(t, e) { var n = undefined; if (!this._eventCollection || !(n = this._eventCollection[t])) { return this } n.forEach(function (t, i) { if (t === e || t.listener === e) { n.splice(i, 1) } }); if (n.length === 0) { delete this._eventCollection[t] } return this }; t.prototype.emit = function s(t) { var e = this; for (var n = arguments.length, i = Array(n > 1 ? n - 1 : 0), o = 1; o < n; o++) { i[o - 1] = arguments[o] } var s = undefined; if (!this._eventCollection || !(s = this._eventCollection[t])) { return this } s = s.slice(0); s.forEach(function (t) { return t.apply(e, i) }); return this }; return t }(); n["default"] = o; e.exports = n["default"] }, {}] }, {}, [1])(1) }); \ No newline at end of file diff --git a/ydoc/styles/style.css b/ydoc/styles/style.css new file mode 100644 index 0000000..632e171 --- /dev/null +++ b/ydoc/styles/style.css @@ -0,0 +1,2281 @@ +@charset "UTF-8"; +/* + * by wenbo.dong@qunar.com + * method color + * param $color: color HEX + * param $index: color number +*/ +body { + font-size: 14px; + color: rgba(3, 17, 31, 0.87); + -webkit-font-smoothing: antialiased; + line-height: 1.5; +} + +@font-face { + font-family: 'ydoc'; + src: url("https://s.qunarzz.com/ydoc/fonts/0.0.4/ydoc.eot"); + /* IE9*/ + src: url("https://s.qunarzz.com/ydoc/fonts/0.0.4/ydoc.woff") format("woff"), url("https://s.qunarzz.com/ydoc/fonts/0.0.4/ymfe.ttf") format("truetype"), url("https://s.qunarzz.com/ydoc/fonts/0.0.4/ydoc.svg#iconfont") format("svg"); + /* iOS 4.1- */ +} + +.ui-font-ydoc { + font-family: ydoc; +} + +::-webkit-scrollbar { + width: 6px; + height: 4px; +} + +::-webkit-scrollbar-track { + -webkit-box-shadow: rgba(217, 237, 255, 0.3); + background: rgba(217, 237, 255, 0.1); +} + +::-webkit-scrollbar-thumb { + border-radius: 3px; + background: rgba(101, 181, 255, 0.3); + -webkit-box-shadow: rgba(217, 237, 255, 0.5); +} + +::-webkit-scrollbar-thumb:window-inactive { + background: rgba(101, 181, 255, 0.3); +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5em; + font-weight: 400; + line-height: 1.5; +} + +p { + margin-top: 0; + margin-bottom: 1em; +} + +h1, .h1 { + font-size: 32px; +} + +h2, .h2 { + font-size: 24px; +} + +h3, .h3 { + font-size: 20px; +} + +h4, .h4 { + font-size: 16px; +} + +h5, .h5 { + font-size: 14px; +} + +h6, .h6 { + font-size: 12px; +} + +@media screen and (max-width: 960px) { + ::-webkit-scrollbar { + /*隐藏滚轮*/ + display: none; + } +} + +*, +::before, +::after { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + font-size: 100px; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif; +} + +ul, +ol, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +figure, +form, +fieldset, +legend, +input, +textarea, +button, +p, +blockquote, +th, +td, +pre, +xmp { + margin: 0; + padding: 0; +} + +input, +textarea, +button, +select, +pre, +xmp, +tt, +code, +kbd, +samp { + line-height: inherit; + font-family: inherit; +} + +table { + border-collapse: collapse; + border-spacing: 0; + table-layout: fixed; + text-align: left; +} + +fieldset, +img { + border: 0; + vertical-align: middle; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +main, +menu, +nav, +section, +summary { + display: block; +} + +audio, +canvas, +video { + display: inline-block; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: "\0020"; +} + +textarea { + resize: vertical; +} + +input, +textarea, +button, +select +a { + outline: 0 none; +} + +input, +textarea, +button, +select { + color: inherit; +} + +input:disabled, +textarea:disabled, +button:disabled, +select:disabled { + opacity: 1; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +mark { + background-color: rgba(0, 0, 0, 0); +} + +a, +ins, +s, +u, +del { + text-decoration: none; +} + +a, +img { + -webkit-touch-callout: none; +} + +a:link, a:visited { + color: inherit; +} + +.row { + box-sizing: border-box; + width: 100%; +} + +.row:after { + content: ''; + display: table; + clear: both; +} + +.container { + box-sizing: border-box; +} + +@media screen and (max-width: 600px) { + .col-xs-1 { + width: 8.33333%; + float: left; + box-sizing: border-box; + } + .col-xs-2 { + width: 16.66667%; + float: left; + box-sizing: border-box; + } + .col-xs-3 { + width: 25%; + float: left; + box-sizing: border-box; + } + .col-xs-4 { + width: 33.33333%; + float: left; + box-sizing: border-box; + } + .col-xs-5 { + width: 41.66667%; + float: left; + box-sizing: border-box; + } + .col-xs-6 { + width: 50%; + float: left; + box-sizing: border-box; + } + .col-xs-7 { + width: 58.33333%; + float: left; + box-sizing: border-box; + } + .col-xs-8 { + width: 66.66667%; + float: left; + box-sizing: border-box; + } + .col-xs-9 { + width: 75%; + float: left; + box-sizing: border-box; + } + .col-xs-10 { + width: 83.33333%; + float: left; + box-sizing: border-box; + } + .col-xs-11 { + width: 91.66667%; + float: left; + box-sizing: border-box; + } + .col-xs-12 { + width: 100%; + float: left; + box-sizing: border-box; + } + .container { + width: 100%; + padding: 0 .16rem; + } +} + +@media screen and (min-width: 600px) { + .col-sm-1 { + width: 8.33333%; + float: left; + box-sizing: border-box; + } + .col-sm-2 { + width: 16.66667%; + float: left; + box-sizing: border-box; + } + .col-sm-3 { + width: 25%; + float: left; + box-sizing: border-box; + } + .col-sm-4 { + width: 33.33333%; + float: left; + box-sizing: border-box; + } + .col-sm-5 { + width: 41.66667%; + float: left; + box-sizing: border-box; + } + .col-sm-6 { + width: 50%; + float: left; + box-sizing: border-box; + } + .col-sm-7 { + width: 58.33333%; + float: left; + box-sizing: border-box; + } + .col-sm-8 { + width: 66.66667%; + float: left; + box-sizing: border-box; + } + .col-sm-9 { + width: 75%; + float: left; + box-sizing: border-box; + } + .col-sm-10 { + width: 83.33333%; + float: left; + box-sizing: border-box; + } + .col-sm-11 { + width: 91.66667%; + float: left; + box-sizing: border-box; + } + .col-sm-12 { + width: 100%; + float: left; + box-sizing: border-box; + } + .container { + width: 100%; + padding: 0 .16rem; + } +} + +@media screen and (min-width: 960px) { + .col-md-1 { + width: 8.33333%; + float: left; + box-sizing: border-box; + } + .col-md-2 { + width: 16.66667%; + float: left; + box-sizing: border-box; + } + .col-md-3 { + width: 25%; + float: left; + box-sizing: border-box; + } + .col-md-4 { + width: 33.33333%; + float: left; + box-sizing: border-box; + } + .col-md-5 { + width: 41.66667%; + float: left; + box-sizing: border-box; + } + .col-md-6 { + width: 50%; + float: left; + box-sizing: border-box; + } + .col-md-7 { + width: 58.33333%; + float: left; + box-sizing: border-box; + } + .col-md-8 { + width: 66.66667%; + float: left; + box-sizing: border-box; + } + .col-md-9 { + width: 75%; + float: left; + box-sizing: border-box; + } + .col-md-10 { + width: 83.33333%; + float: left; + box-sizing: border-box; + } + .col-md-11 { + width: 91.66667%; + float: left; + box-sizing: border-box; + } + .col-md-12 { + width: 100%; + float: left; + box-sizing: border-box; + } + .container { + width: 100%; + padding: 0 .16rem; + } +} + +@media screen and (min-width: 1440px) { + .col-lg-1 { + width: 8.33333%; + float: left; + box-sizing: border-box; + } + .col-lg-2 { + width: 16.66667%; + float: left; + box-sizing: border-box; + } + .col-lg-3 { + width: 25%; + float: left; + box-sizing: border-box; + } + .col-lg-4 { + width: 33.33333%; + float: left; + box-sizing: border-box; + } + .col-lg-5 { + width: 41.66667%; + float: left; + box-sizing: border-box; + } + .col-lg-6 { + width: 50%; + float: left; + box-sizing: border-box; + } + .col-lg-7 { + width: 58.33333%; + float: left; + box-sizing: border-box; + } + .col-lg-8 { + width: 66.66667%; + float: left; + box-sizing: border-box; + } + .col-lg-9 { + width: 75%; + float: left; + box-sizing: border-box; + } + .col-lg-10 { + width: 83.33333%; + float: left; + box-sizing: border-box; + } + .col-lg-11 { + width: 91.66667%; + float: left; + box-sizing: border-box; + } + .col-lg-12 { + width: 100%; + float: left; + box-sizing: border-box; + } + .container { + width: 100%; + padding: 0 .16rem; + } +} + +.useage1 { + background-color: #d9edff; +} + +.useage2 { + background-color: #b2daff; +} + +.useage3 { + background-color: #8cc8ff; +} + +.useage4 { + background-color: #65b5ff; +} + +.useage5 { + background-color: #3fa3ff; +} + +.useage6 { + background-color: #1890ff; +} + +.useage7 { + background-color: #1373cc; +} + +.useage8 { + background-color: #0e5699; +} + +.useage9 { + background-color: #0a3a66; +} + +.useage10 { + background-color: #051d33; +} + +/*! responsive-nav.js 1.0.39 by @viljamis */ +.m-header-nav ul { + margin: 0; + padding: 0; + width: 100%; + display: block; + list-style: none; +} + +.js .m-header-nav { + clip: rect(0 0 0 0); + max-height: 0; + position: absolute; + display: block; + overflow: hidden; + zoom: 1; +} + +.m-header-nav.opened { + max-height: 9999px; +} + +.nav-toggle { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +@media screen and (min-width: 960px) { + .js .m-header-nav { + position: relative; + } + .js .m-header-nav.closed { + max-height: none; + } + .nav-toggle { + display: none; + } + .m-header-btn { + display: none; + } +} + +html { + height: 100%; +} + +body { + width: 100%; + height: 100%; +} + +.slideout-menu { + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 0; + width: 256px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + display: none; +} + +.slideout-panel { + position: relative; + z-index: 1; + will-change: transform; +} + +.slideout-open, +.slideout-open body, +.slideout-open .slideout-panel { + overflow: hidden; +} + +.slideout-open .slideout-menu { + display: block; +} + +@media screen and (min-width: 960px) { + .slideout-menu { + display: block; + } + .slideout-panel { + will-change: inherit; + } +} + +@font-face { + font-family: octicons-link; + src: url(data:font/woff;charset=utf-8;base64,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) format("woff"); +} + +.markdown-body { + box-sizing: border-box; + min-width: 200px; + margin: 0 auto; + padding: 0; + transition: all 0.2s; +} + +@media screen and (min-width: 960px) { + .markdown-body { + padding: 0 .4rem; + } +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: #24292e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: #24292e; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: #032f62; +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + color: #fafbfc; + background-color: #b31d28; +} + +.markdown-body .pl-c2 { + color: #fafbfc; + background-color: #d73a49; +} + +.markdown-body .pl-c2::before { + content: "^M"; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: #22863a; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: #005cc5; +} + +.markdown-body .pl-mi { + font-style: italic; + color: #24292e; +} + +.markdown-body .pl-mb { + font-weight: bold; + color: #24292e; +} + +.markdown-body .pl-md { + color: #b31d28; + background-color: #ffeef0; +} + +.markdown-body .pl-mi1 { + color: #22863a; + background-color: #f0fff4; +} + +.markdown-body .pl-mc { + color: #e36209; + background-color: #ffebda; +} + +.markdown-body .pl-mi2 { + color: #f6f8fa; + background-color: #005cc5; +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: #6f42c1; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #032f62; +} + +.markdown-body .octicon { + display: inline-block; + vertical-align: text-top; + fill: currentColor; +} + +.markdown-body a { + background-color: transparent; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; +} + +.markdown-body strong { + font-weight: bolder; +} + +.markdown-body h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre { + font-family: monospace, monospace; + font-size: 1em; +} + +.markdown-body hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type="checkbox"] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: #0366d6; + text-decoration: none; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; +} + +.markdown-body hr::before { + display: table; + content: ""; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body h1 { + font-size: 32px; + font-weight: 600; +} + +.markdown-body h2 { + font-size: 24px; + font-weight: 600; +} + +.markdown-body h3 { + font-size: 20px; + font-weight: 600; +} + +.markdown-body h4 { + font-size: 16px; + font-weight: 600; +} + +.markdown-body h5 { + font-size: 14px; + font-weight: 600; +} + +.markdown-body h6 { + font-size: 12px; + font-weight: 600; +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ul, +.markdown-body ol { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code { + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; +} + +.markdown-body .octicon { + vertical-align: text-bottom; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .pl-3 { + padding-left: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body hr { + height: 0.12em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.markdown-body blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 { + padding-bottom: 0.3em; + font-size: 2em; +} + +.markdown-body h2 { + padding-bottom: 0.3em; + font-size: 1.5em; +} + +.markdown-body h3 { + font-size: 1.25em; +} + +.markdown-body h4 { + font-size: 1em; +} + +.markdown-body h5 { + font-size: 0.875em; +} + +.markdown-body h6 { + font-size: 0.85em; + color: #6a737d; +} + +.markdown-body ul, +.markdown-body ol { + padding-left: 2em; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body img { + max-width: 100%; + box-sizing: content-box; + background-color: #fff; +} + +.markdown-body img[align=right] { + padding-left: 20px; +} + +.markdown-body img[align=left] { + padding-right: 20px; +} + +.markdown-body code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; +} + +.markdown-body pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .full-commit .btn-outline:not(:disabled):hover { + color: #005cc5; + border-color: #005cc5; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #d1d5da; + border-bottom-color: #c6cbd1; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #c6cbd1; +} + +.markdown-body :checked + .radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} + +.markdown-body hr { + border-bottom-color: #eee; +} + +.markdown-body { + color: rgba(3, 17, 31, 0.87); +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + font-weight: 500; +} + +.markdown-body h2 { + margin: .54rem 0 .24rem; +} + +.markdown-body h3 { + margin: .32rem 0 .16rem; +} + +.markdown-body a { + text-decoration: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + color: #086fff; + transition: all .2s; +} + +.markdown-body a:hover { + text-decoration: none; + border-bottom-color: rgba(3, 17, 31, 0.87); +} + +.markdown-body .type { + color: #1890ff; + font-size: 16px; +} + +.markdown-body .versionTag { + background: #1890ff; + color: white; + border-radius: 4px; + padding: 2px 4px; + font-size: 14px; +} + +.markdown-body .desc { + font-size: 16px; + font-weight: normal; + color: rgba(0, 0, 0, 0.65); +} + +/* PrismJS 1.14.0 +http://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: rgba(255, 255, 255, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.g-doc { + display: flex; + width: 100%; + height: 100%; + position: relative; + left: 0; + transition: all 0.2s ease-in-out; + overflow: hidden; +} + +.m-main { + width: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.m-main h2:before, .m-main h3:before { + content: ""; + display: block; + margin-top: -.8rem; + height: .8rem; + visibility: hidden; +} + +@media screen and (min-width: 960px) { + .m-main { + box-shadow: none; + } +} + +.m-header { + min-height: .64rem; + position: relative; + display: flex; + flex-direction: column; + position: fixed; + top: 0; + left: 0; + width: 100%; + color: rgba(3, 17, 31, 0.54); + background-color: rgba(255, 255, 255, 0.97); + border-bottom: 1px solid #e5f0f6; + backface-visibility: hidden; + z-index: 999; + transform: translateZ(1000px); +} + +.m-header-title { + height: .64rem; + padding: 0 .3rem; + display: flex; + align-items: center; +} + +.m-header-title .name, .m-header-title .logo { + display: inline-block; + vertical-align: middle; +} + +.m-header-title .name { + margin-left: .08rem; + color: #1890ff; + font-size: 24px; + font-family: 'Arial Rounded MT Bold'; +} + +.m-header-nav { + border-bottom: 1px solid #e5f0f6; +} + +.m-header-nav .m-header-items { + padding: 0.16rem; +} + +.m-header-nav .m-header-subtitle { + padding: .08rem 1.5em; +} + +.m-header-nav .m-header-subtitle .item { + width: 100%; +} + +.m-header-nav .m-header-subtitle .link { + display: inline-block; + padding-left: .5em; + padding-right: .5em; + width: 100%; + background-color: transparent; + border-radius: 4px; + line-height: .32rem; + transition: all 0.2s; +} + +.m-header-nav .m-header-subtitle .link:hover { + color: #086fff; + background-color: #d9edff; +} + +.m-header-nav .item { + width: 100%; + display: inline-block; + line-height: 1.8; + padding: 0 0.08rem; + position: relative; + cursor: pointer; + transition: all 0.2s; + min-height: .32rem; +} + +.m-header-nav .item:last-child { + margin-bottom: 0; +} + +.m-header-nav .item.active { + font-weight: bold; + color: #086fff; +} + +.m-header-nav .href { + display: inline-block; + padding-left: .5em; + padding-right: .5em; + height: .32rem; + line-height: .32rem; + width: 100%; + background-color: transparent; + border-radius: 4px; + transition: all 0.2s; +} + +.m-header-nav .href:active { + color: #086fff; + background-color: #d9edff; +} + +.m-header-btn { + position: absolute; + top: 0.08rem; + right: 0.08rem; + font-size: .24rem; + line-height: 1; + color: rgba(3, 17, 31, 0.87); + border-radius: 2px; + padding: .12rem; + cursor: pointer; + transition: color 0.2s; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.m-header-btn:hover { + font-weight: bold; + color: rgba(3, 17, 31, 0.87); +} + +@media screen and (min-width: 960px) { + .m-header { + flex-direction: row; + } + .m-header-nav { + border: none; + } + .m-header-nav .m-header-items { + padding: 0; + } + .m-header-nav .m-header-items > .item:hover .m-header-subtitle { + display: flex; + flex-wrap: wrap; + } + .m-header-nav .m-header-subtitle { + display: none; + position: absolute; + left: 50%; + top: .64rem; + transform: translateX(-50%); + width: auto; + background-color: #fff; + padding: .08rem 1.5em; + border-radius: 4px; + box-shadow: 0 10px 100px rgba(50, 50, 93, 0.1), 0 5px 35px rgba(50, 50, 93, 0.15), 0 2px 15px rgba(0, 0, 0, 0.1); + } + .m-header-nav .m-header-subtitle .item { + margin: 0; + padding: 0 8px; + color: rgba(3, 17, 31, 0.54); + font-weight: normal; + white-space: nowrap; + line-height: .4rem; + } + .m-header-nav .m-header-subtitle:after { + content: ''; + display: block; + width: 0; + height: 0; + border: .08rem solid transparent; + border-bottom-color: #fff; + position: absolute; + left: 50%; + top: -.16rem; + transform: translateX(-50%); + } + .m-header-nav .m-header-subtitle .link { + display: inline-block; + padding: 0; + width: auto; + } + .m-header-nav .m-header-subtitle .link:hover { + font-weight: bold; + color: rgba(3, 17, 31, 0.87); + background-color: transparent; + } + .m-header-nav .href { + border-radius: 0; + } + .m-header-nav .href:hover { + font-weight: bold; + color: rgba(3, 17, 31, 0.87); + background-color: transparent; + } + .m-header-nav .item { + margin: 0 0.16rem; + line-height: .64rem; + width: auto; + transition: color 0.2s; + } + .m-header-nav .item.active { + font-weight: bold; + color: rgba(3, 17, 31, 0.87); + } + .m-header-nav .item.active .href { + border-bottom: 2px solid #086fff; + } + .m-header .m-header-nav { + overflow: inherit; + } +} + +.m-aside { + margin-top: .65rem; + height: 100%; + height: calc(100% - .65rem); +} + +.m-summary { + padding: 0 .24rem; + position: absolute; + left: 0; + top: 0; + z-index: 1; + width: 100%; + height: 0; + overflow-y: auto; + transform: translateY(0); + -webkit-overflow-scrolling: touch; + transition: all .2s ease-in-out; +} + +.m-summary ul, .m-summary ol, .m-summary li { + list-style: none; +} + +.m-summary.active { + height: 100%; + background-color: #fff; + padding: .24rem; + padding-bottom: 0.89rem; + transform: translateY(0.65rem); +} + +.m-summary.active .m-summary-content { + display: block; +} + +.m-summary.active + .m-summary-switch .top { + transform: translateY(10px); +} + +.m-summary.active + .m-summary-switch .bottom { + transform: translateY(-10px); +} + +.m-summary-content { + width: 100%; + height: auto; + display: none; +} + +.m-summary-block { + margin-bottom: .16rem; +} + +.m-summary-block.active > .href { + font-weight: bold; + color: #086fff; +} + +.m-summary-title { + font-size: 16px; + line-height: 2.5; + color: rgba(3, 17, 31, 0.87); + padding-left: 0.08rem; +} + +.m-summary-list { + font-size: 14px; + line-height: 2.5; +} + +.m-summary-list.indent { + padding-left: 1.5em; +} + +.m-summary-list .item { + color: rgba(3, 17, 31, 0.54); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-summary-list .item.active { + font-weight: bold; + color: #086fff; +} + +.m-summary-list .href { + color: inherit; + background-color: transparent; + transition: all 0.2s; + display: inline-block; + width: 100%; + border-radius: 4px; + padding-left: 0.08rem; +} + +.m-summary-list .href:hover { + color: #086fff; + background-color: #f1f6fe; +} + +.m-summary-switch { + font-family: 'ydoc'; + position: fixed; + right: .24rem; + bottom: .48rem; + width: .6rem; + height: .6rem; + border-radius: 50%; + color: #086fff; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); + background-color: rgba(3, 17, 31, 0.87); + user-select: none; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column-reverse; + z-index: 1; +} + +.m-summary-switch .top, .m-summary-switch .bottom { + transition: all 0.2s ease-in-out; +} + +@media screen and (min-width: 960px) { + .m-summary { + position: static; + height: 100%; + width: 2.4rem; + padding: .4rem .32rem; + flex: 3rem 0 0; + } + .m-summary-switch { + display: none; + } + .m-summary-content { + display: block; + } +} + +@media screen and (min-width: 1440px) { + .m-summary { + width: 3rem; + } +} + +.m-content { + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + background-color: #fff; + padding-top: .64rem; + height: 100%; +} + +.m-content-container { + padding: .48rem .25rem 1.2rem; + margin: 0 auto; +} + +.m-content-container .title { + line-height: 1; +} + +.m-content-container.markdown-body { + min-height: calc(100% - 2rem); +} + +.m-paging { + font-size: 16px; +} + +.m-paging-item { + width: 50%; + color: inherit; + transition: color 0.2s; +} + +.m-paging-item:hover { + color: #086fff; +} + +.m-paging-prev { + float: left; +} + +.m-paging-next { + float: right; + text-align: right; +} + +.m-paging:after { + content: ''; + display: block; + clear: both; +} + +.m-paging .href { + display: inline-block; + width: 100%; + padding: .04rem; +} + +@media screen and (min-width: 960px) { + .m-content { + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } +} + +.m-footer { + background-color: #fff; + padding: .24rem; +} + +.m-footer ul, .m-footer ol, .m-footer li { + list-style: none; +} + +.m-footer-container { + display: flex; + flex-direction: column; + padding: 0; + transition: all 0.2s; +} + +.m-footer-title { + text-align: center; +} + +.m-footer-links { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.m-footer-links .group { + flex-basis: 30%; + flex-shrink: 0; + margin-bottom: .24rem; +} + +.m-footer-links .title { + font-size: 16px; + line-height: 2; + color: rgba(3, 17, 31, 0.38); +} + +.m-footer .href { + line-height: 1.8; + padding: .3em 0; + color: rgba(3, 17, 31, 0.54); + background-color: #fff; + transition: all 0.2s; +} + +.m-footer .href:hover { + color: #086fff; +} + +.m-footer-title { + color: rgba(3, 17, 31, 0.54); +} + +@media screen and (min-width: 960px) { + .m-footer { + font-size: 16px; + color: rgba(3, 17, 31, 0.54); + padding: .24rem 0rem; + } + .m-footer-container { + margin: .24rem auto; + padding: 0; + } + .m-footer-title { + flex: 1; + padding-top: .25rem; + border-top: solid 1px #999999; + } + .m-footer-links { + width: 90%; + margin: 0px auto; + justify-content: inherit; + flex: 2; + } +} + +.g-home .m-header { + background-color: #fbfbfb; +} + +.g-home .m-section { + position: relative; +} + +.g-home .m-section-container { + max-width: 10.24rem; + margin: 0 auto; + padding: .8rem .32rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.g-home .m-section-title { + text-align: center; +} + +.g-home .m-section-title .name { + margin-bottom: .24rem; + font-size: 32px; + position: relative; +} + +.g-home .m-section-title .name:after { + content: ''; + display: block; + position: absolute; + bottom: -.08rem; + left: 50%; + transform: translateX(-50%); + width: .48rem; + height: 3px; + background-color: #1373cc; +} + +.g-home .m-section-title .desc { + font-size: 16px; + color: rgba(3, 17, 31, 0.54); + line-height: 1.6; +} + +.g-home .m-section-banner { + width: 100%; + position: absolute; + z-index: 0; + bottom: 0; + left: 50%; + transform: translateX(-45%); +} + +.g-home .m-section-banner img { + width: 100%; +} + +.g-home .m-section-box { + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.g-home .m-section .btn { + width: 1.2rem; + padding: .14rem; + line-height: .2rem; + font-size: 16px; + color: #086fff; + background-color: #fff; + margin-right: .08rem; + border-radius: 6px; + text-align: center; + user-select: none; + border: 1px solid #086fff; + cursor: pointer; + transition: all 0.2s; + transform: translateY(0); +} + +.g-home .m-section .btn:hover { + color: #fff; + background-color: #086fff; +} + +.g-home .m-section .btn:active { + background-color: #0e5699; +} + +.g-home .m-section .btn.btn-ghost { + border: none; +} + +.g-home .m-section.home { + padding: .56rem .24rem .64rem; + border-bottom: 1px solid rgba(3, 17, 31, 0.14); +} + +.g-home .m-section.home .m-section-container { + padding: .24rem .32rem; +} + +.g-home .m-section.home .m-section-title { + text-align: center; + color: rgba(3, 17, 31, 0.87); + flex: 1; +} + +.g-home .m-section.home .m-section-title .name { + margin-bottom: .16rem; +} + +.g-home .m-section.home .m-section-title .name:after { + display: none; +} + +.g-home .m-section.home .m-section-title .desc { + color: rgba(3, 17, 31, 0.54); +} + +.g-home .m-section.home .m-section-btngroup { + display: flex; + justify-content: center; + padding: .24rem 0 .16rem; + position: relative; + z-index: 1; +} + +.g-home .m-section.home .caption { + font-size: 16px; + color: rgba(3, 17, 31, 0.38); +} + +.g-home .m-section.feature { + background-color: #fbfbfb; + border-bottom: 1px solid rgba(3, 17, 31, 0.14); +} + +.g-home .m-section.feature .item { + flex: 0 0 46%; + margin-bottom: .32rem; +} + +.g-home .m-section.feature .item .title { + font-size: 32px; + margin-bottom: .16rem; +} + +.g-home .m-section.feature .item .desc { + font-size: 16px; + color: rgba(3, 17, 31, 0.54); +} + +@media screen and (min-width: 960px) { + .g-home .m-section.home .m-section-container { + flex-direction: row; + } + .g-home .m-section-title .name { + font-size: 48px; + } + .g-home .m-section-title .desc { + font-size: 20px; + line-height: 1.6; + } + .g-home .m-section-banner { + width: 7.7rem; + } + .g-home .m-section .btn { + margin-right: .16rem; + } + .g-home .m-section.home .m-section-title { + text-align: left; + } + .g-home .m-section.home .m-section-title .name { + font-size: 48px; + } + .g-home .m-section.home .m-section-title .desc { + font-size: 20px; + line-height: 1.6; + } + .g-home .m-section.home .m-section-btngroup { + justify-content: left; + margin-top: .24rem; + } + .g-home .m-footer .m-footer-container { + max-width: 10.24rem; + } +} + +* { + margin: 0; + padding: 0; +} diff --git a/ydoc/ydoc-plugin-search/core.js b/ydoc/ydoc-plugin-search/core.js new file mode 100644 index 0000000..ea37963 --- /dev/null +++ b/ydoc/ydoc-plugin-search/core.js @@ -0,0 +1,104 @@ +$(function() { + var num = 0; + var searchText; + var searchMaxNum = 8; + var releativePath = document.getElementById('releativePath').getAttribute('content') || '' + releativePath = releativePath.trim() + releativePath = releativePath === '' ? '.' : releativePath + + window.ydoc_plugin_search_core = function(text) { + if(!text)return; + var json = cloneObject(window.ydoc_plugin_search_json); + searchText = text; + num = 0; + var result = search(json) + return result; + }; + + function cloneObject(obj) { + if (typeof obj === 'object') { + if (Array.isArray(obj)) { + var newArr = []; + obj.forEach(function(item, index){ + newArr[index] = cloneObject(item) + }) + return newArr; + } else { + var newObj = {}; + for (var key in obj) { + newObj[key] = cloneObject(obj[key]); + } + return newObj; + } + } else { + return obj; + } + } + + + function search(json){ + var result = {} + for(var i in json){ + var data = searchBook(json[i]) + data = filterBook(data); + if(data.length > 0) result[i] = data; + } + return result; + } + + function filterBook(newPages){ + return newPages.filter(function(page){ + if(!page.content && page.children.length === 0){ + return false; + } + return true; + }) + } + + function searchBook(pages){ + var newPages = []; + if(num > searchMaxNum){ + return newPages; + } + for(var i=0, l=pages.length; i< l; i++){ + var page = pages[i], searchPage = null; + if(num > searchMaxNum){ + return newPages; + } + if(page.content && page.content.toLowerCase().indexOf(searchText.toLowerCase()) !== -1){ + num++; + searchPage = { + title: page.title, + content: page.content, + url: releativePath + page.url, + children: [] + }; + }else{ + searchPage = { + title: page.title, + url: releativePath + page.url, + children: [] + }; + } + + newPages.push(searchPage) + + if(page.children && Array.isArray(page.children)){ + for(var j=0, len=page.children.length; j< len; j++){ + var child = page.children[j]; + if(num >= searchMaxNum ){ + return newPages; + } + if(child.content && child.content.toLowerCase().indexOf(searchText.toLowerCase()) !== -1){ + child.url = releativePath + child.url; + searchPage.children.push(child) + num++; + } + } + } + } + return newPages; + } + + +}); diff --git a/ydoc/ydoc-plugin-search/search.css b/ydoc/ydoc-plugin-search/search.css new file mode 100644 index 0000000..b08cec4 --- /dev/null +++ b/ydoc/ydoc-plugin-search/search.css @@ -0,0 +1,118 @@ +.m-search { + position: absolute; + right: .64rem; + top: .32rem; + transform: translateY(-50%); + z-index: 9999; +} +.m-search-result { + display: none; + position: absolute; + width: 100vw; + width: calc(100vw - 32px); + right: -.48rem; + top: .48rem; + background-color: #fff; + border-radius: 4px; + box-shadow: 0 50px 100px rgba(50, 50, 93, 0.1), 0 15px 35px rgba(50, 50, 93, 0.15), 0 5px 15px rgba(0, 0, 0, 0.1); + overflow: hidden; +} +.m-search .highlight { + font-weight: bold; + color: #1890ff; +} +.m-search .icon { + font-family: ydoc; + position: absolute; + left: 16px; + top: 16px; + transform: translate(-50%, -50%); + font-size: 18px; +} +.m-search .input { + width: 72px; + border: none; + outline: none; + height: 32px; + line-height: 32px; + padding: 0 .08rem 0 .32rem; + font-size: 14px; + border-radius: 4px; + transition: all .2s ease-in-out; +} +.m-search .input:focus { + width: 160px; + background-color: #f7f7f7; +} +.m-search .empty { + padding: .32rem .24rem; + text-align: center; +} +.m-search .headline { + padding: .08rem; + background-color: #1890ff; + color: #fff; + font-weight: bold; +} +.m-search .row { + display: flex; + margin: .08rem 0; +} +.m-search .subtitle { + flex: 0 0 25%; + text-align: right; + padding: .04rem .08rem; + background-color: #fff; + cursor: pointer; + transition: all .2s ease-in-out; +} +.m-search .subtitle:hover { + background-color: #f7f7f7; +} +.m-search .content { + flex: 1; + font-weight: bold; + border-left: 1px solid rgba(3, 17, 31, 0.14); + padding: 0; + padding-right: .08rem; +} +.m-search .caption { + padding: .04rem 0; + padding-left: 0.08rem; + background-color: #fff; + cursor: pointer; + transition: all .2s ease-in-out; + display: flex; + flex-direction: column; +} +.m-search .caption:hover { + background-color: #f7f7f7; +} +.m-search .caption.active { + background-color: #f7f7f7; +} +.m-search .caption .desc { + font-weight: normal; +} + +/* 宽度小于 600px 时搜索结果横贯屏幕宽度 */ +@media screen and (min-width: 600px) { + .m-search-result { + position: absolute; + right: 0; + width: 4rem; + } + .m-search .input:focus { + width: 180px; + } +} + +/* PC 端不显示 nav 按钮 因此更靠右侧 */ +@media screen and (min-width: 960px) { + .m-search { + right: .24rem; + } + .m-search .input:focus { + width: 200px; + } +} \ No newline at end of file diff --git a/ydoc/ydoc-plugin-search/search.js b/ydoc/ydoc-plugin-search/search.js new file mode 100644 index 0000000..582b00c --- /dev/null +++ b/ydoc/ydoc-plugin-search/search.js @@ -0,0 +1,127 @@ +$(function(){ + var $searchResult = $('.js-search-result'), + $searchInput = $('.js-input'), + activeIndex = 0; + + // 去除空格 + String.prototype.trim = function () { + return this.replace(/(^\s*)|(\s*$)/g, ''); + }; + + // 判断是否为空对象 + function realObj(obj) { + if (JSON.stringify(obj) === '{}') { + return false; // 如果为空,返回false + } + return true; + } + + // 防抖函数 + function debounce(func, wait) { + var timeout; + return function () { + var context = this, args = arguments; + clearTimeout(timeout); + timeout = setTimeout(function() { + func.apply(context, args); + }, wait); + }; + } + + var highlightTextPrevNum = 6; + var highlightTextNextNum = 20; + // 简化文本内容长度 + function simplifyStrDom(str, val) { + var index = str.indexOf(val); + var startIndex = index > highlightTextPrevNum ? index - highlightTextPrevNum : 0; + var sliceStr = str.slice(startIndex, index + val.length + highlightTextNextNum); + var reg = new RegExp('(' + val + ')', 'gi'); // 搜索的值进行高亮替换时, 忽略大小写 + var addHighlightStr = sliceStr.replace(reg, '' + '$1' + ''); + var ellipsis = (sliceStr.lastIndexOf(val) != -1) || (sliceStr.lastIndexOf(val) > highlightTextNextNum) ? '...' : ''; + return addHighlightStr + ellipsis; + } + + // 隐藏搜索结果框 + // function hideSearchResult() { + // $searchResult.hide(); + // } + + // 监听输入的内容 + $searchInput.on('input', debounce(function(e) { + var val = e.target.value.trim(), + res = window.ydoc_plugin_search_core(val); + + activeIndex = 0; + $(document).off('keydown'); + $searchResult.show(); + if (realObj(res) || val === '') { + var dom = ''; + for (var key in res) { + dom += '
' + key + '
'; + res[key].forEach(function(item) { + var contentDom = ''; + if (item.children.length) { + item.children.forEach(function (i) { + i.title = simplifyStrDom(i.title, val); + i.content = simplifyStrDom(i.content, val); + contentDom += '
' + + '
' + i.title + '
' + + '
' + i.content + '
'; + }); + } else { + item.title = simplifyStrDom(item.title, val); + item.content = simplifyStrDom(item.content, val); + contentDom = '' + + '
' + item.title + '
' + + '
' + item.content + '
'; + } + dom += '
' + + '' + item.title + '' + + '
' + contentDom + '
' + + '
'; + }); + } + $searchResult.html(dom); + + var $captions = $('.js-search-result .caption'); + var length = $captions.length; + if ($captions.length) { + $captions[activeIndex].classList.add('active'); + // 监听键盘事件 up: 38, down: 40, enter: 13 + $(document).on('keydown', function (e) { + if (e.keyCode == 38) { + $captions[activeIndex].classList.remove('active'); + activeIndex = (activeIndex + length - 1) % length; + $captions[activeIndex].classList.add('active'); + } else if (e.keyCode == 40) { + $captions[activeIndex].classList.remove('active'); + activeIndex = (activeIndex + 1) % length; + $captions[activeIndex].classList.add('active'); + } else if (e.keyCode == 13) { + $searchResult.hide(); + window.open($captions[activeIndex].href, '_self'); + } + }); + } + // 按下 ESC 键,清空输入框并收起搜索结果框 + $(document).on('keydown', function (e) { + if (e.keyCode == 27) { + $searchInput[0].value = ''; + $searchResult.hide(); + } + }); + } else { + $searchResult.html('
没有找到关键词 ' + val + ' 的搜索结果
') + } + }, 300)); + + $searchResult.on('click', function (e) { + return false; + }) + + $(document).on('click', function(e){ + $searchResult.hide(); + }) + + +}) \ No newline at end of file